diff --git a/Cargo.lock b/Cargo.lock index 4a5a511..1a8a720 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -419,6 +419,7 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer 0.10.4", "crypto-common", + "subtle", ] [[package]] @@ -734,6 +735,15 @@ dependencies = [ "digest 0.9.0", ] +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.7", +] + [[package]] name = "hmac-drbg" version = "0.3.0" @@ -741,9 +751,15 @@ source = "git+https://github.com/Rexagon/rust-hmac-drbg#c8634165e88fafdda5f0702f dependencies = [ "digest 0.9.0", "generic-array", - "hmac", + "hmac 0.11.0", ] +[[package]] +name = "hmac-sha512" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77e806677ce663d0a199541030c816847b36e8dc095f70dae4a4f4ad63da5383" + [[package]] name = "http" version = "0.2.12" @@ -1013,7 +1029,7 @@ dependencies = [ [[package]] name = "nekoton" version = "0.13.1" -source = "git+https://github.com/broxus/nekoton.git#c933b4632756de787eda244a31ca020ffe4c644a" +source = "git+https://github.com/broxus/nekoton.git#9b79453fdf276df45474d858d7789e48d0faaeee" dependencies = [ "anyhow", "async-trait", @@ -1027,7 +1043,7 @@ dependencies = [ "futures-util", "getrandom", "hex", - "hmac", + "hmac 0.11.0", "log", "nekoton-abi", "nekoton-contracts", @@ -1036,13 +1052,14 @@ dependencies = [ "num-bigint", "once_cell", "parking_lot", - "pbkdf2", + "pbkdf2 0.12.2", "quick_cache", "rand", "secstr", "serde", "serde_json", - "sha2 0.9.9", + "sha2 0.10.9", + "slip10_ed25519", "thiserror 1.0.63", "tiny-bip39", "tiny-hderive", @@ -1058,7 +1075,7 @@ dependencies = [ [[package]] name = "nekoton-abi" version = "0.13.0" -source = "git+https://github.com/broxus/nekoton.git#c933b4632756de787eda244a31ca020ffe4c644a" +source = "git+https://github.com/broxus/nekoton.git#9b79453fdf276df45474d858d7789e48d0faaeee" dependencies = [ "anyhow", "base64 0.13.1", @@ -1085,10 +1102,11 @@ dependencies = [ [[package]] name = "nekoton-contracts" version = "0.13.0" -source = "git+https://github.com/broxus/nekoton.git#c933b4632756de787eda244a31ca020ffe4c644a" +source = "git+https://github.com/broxus/nekoton.git#9b79453fdf276df45474d858d7789e48d0faaeee" dependencies = [ "anyhow", "nekoton-abi", + "nekoton-jetton", "nekoton-utils", "once_cell", "serde", @@ -1101,7 +1119,7 @@ dependencies = [ [[package]] name = "nekoton-derive" version = "0.13.0" -source = "git+https://github.com/broxus/nekoton.git#c933b4632756de787eda244a31ca020ffe4c644a" +source = "git+https://github.com/broxus/nekoton.git#9b79453fdf276df45474d858d7789e48d0faaeee" dependencies = [ "either", "proc-macro2", @@ -1109,10 +1127,27 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "nekoton-jetton" +version = "0.13.0" +source = "git+https://github.com/broxus/nekoton.git#9b79453fdf276df45474d858d7789e48d0faaeee" +dependencies = [ + "anyhow", + "lazy_static", + "nekoton-utils", + "num-bigint", + "num-traits", + "serde", + "sha2 0.10.9", + "ton_abi", + "ton_block", + "ton_types", +] + [[package]] name = "nekoton-proto" version = "0.1.0" -source = "git+https://github.com/broxus/nekoton.git#c933b4632756de787eda244a31ca020ffe4c644a" +source = "git+https://github.com/broxus/nekoton.git#9b79453fdf276df45474d858d7789e48d0faaeee" dependencies = [ "anyhow", "nekoton-abi", @@ -1123,7 +1158,7 @@ dependencies = [ [[package]] name = "nekoton-python" -version = "0.1.23" +version = "0.1.24" dependencies = [ "ahash", "anyhow", @@ -1134,14 +1169,15 @@ dependencies = [ "everscale-asm", "everscale-types", "hex", - "hmac", + "hmac 0.11.0", "log", "nekoton", "nekoton-transport", "num-bigint", + "num-traits", "once_cell", "parking_lot", - "pbkdf2", + "pbkdf2 0.9.0", "pyo3", "pyo3-asyncio", "pyo3-log", @@ -1157,12 +1193,13 @@ dependencies = [ "ton_block", "ton_executor", "ton_types", + "ton_vm", ] [[package]] name = "nekoton-transport" version = "0.13.0" -source = "git+https://github.com/broxus/nekoton.git#c933b4632756de787eda244a31ca020ffe4c644a" +source = "git+https://github.com/broxus/nekoton.git#9b79453fdf276df45474d858d7789e48d0faaeee" dependencies = [ "anyhow", "async-trait", @@ -1173,6 +1210,7 @@ dependencies = [ "nekoton-utils", "reqwest", "serde", + "serde_json", "thiserror 1.0.63", "tokio", ] @@ -1180,18 +1218,18 @@ dependencies = [ [[package]] name = "nekoton-utils" version = "0.13.0" -source = "git+https://github.com/broxus/nekoton.git#c933b4632756de787eda244a31ca020ffe4c644a" +source = "git+https://github.com/broxus/nekoton.git#9b79453fdf276df45474d858d7789e48d0faaeee" dependencies = [ "anyhow", "base64 0.13.1", "chacha20poly1305", "ed25519-dalek", "hex", - "hmac", - "pbkdf2", + "hmac 0.11.0", + "pbkdf2 0.12.2", "secstr", "serde", - "sha2 0.9.9", + "sha2 0.10.9", "thiserror 1.0.63", "ton_block", "ton_types", @@ -1347,11 +1385,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f05894bce6a1ba4be299d0c5f29563e08af2bc18bb7d48313113bed71e904739" dependencies = [ "crypto-mac", - "hmac", + "hmac 0.11.0", "password-hash", "sha2 0.9.9", ] +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest 0.10.7", + "hmac 0.12.1", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -1842,6 +1890,15 @@ dependencies = [ "autocfg", ] +[[package]] +name = "slip10_ed25519" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4be0ff28bf14f9610a342169084e87a4f435ad798ec528dc7579a3678fa9dc9a" +dependencies = [ + "hmac-sha512", +] + [[package]] name = "smallvec" version = "1.13.2" @@ -1990,9 +2047,9 @@ version = "0.8.0" source = "git+https://github.com/broxus/tiny-bip39.git#d2a73124c2fbead4f969f8a5e075ee22040f63cc" dependencies = [ "anyhow", - "hmac", + "hmac 0.11.0", "once_cell", - "pbkdf2", + "pbkdf2 0.9.0", "rand", "rustc-hash", "sha2 0.9.9", @@ -2007,7 +2064,7 @@ version = "0.3.0" source = "git+https://github.com/broxus/tiny-hderive.git#050986d85711497076ba552ce53806885274a4d2" dependencies = [ "base58", - "hmac", + "hmac 0.11.0", "libsecp256k1", "memzero", "sha2 0.9.9", @@ -2094,7 +2151,7 @@ dependencies = [ [[package]] name = "ton_abi" version = "2.1.0" -source = "git+https://github.com/broxus/ton-labs-abi.git#b8834529ef8fa121d534fca6554c0e95d4019998" +source = "git+https://github.com/broxus/ton-labs-abi.git#7d84f87a1799b727e33f9b09c8e38c764fbd5c68" dependencies = [ "anyhow", "base64 0.13.1", @@ -2116,7 +2173,7 @@ dependencies = [ [[package]] name = "ton_block" version = "1.9.73" -source = "git+https://github.com/broxus/ton-labs-block.git#5c4e4f84cebaf34254fd97523d1fc83d7fbe2d91" +source = "git+https://github.com/broxus/ton-labs-block.git#a4fd60ccd6c48e00863dfcf402cb1c045d92a1a3" dependencies = [ "anyhow", "base64 0.13.1", @@ -2138,7 +2195,7 @@ dependencies = [ [[package]] name = "ton_executor" version = "1.15.54" -source = "git+https://github.com/broxus/ton-labs-executor.git#2a38890d53f9c3dad84b3fb9a58e3bb62ba20f44" +source = "git+https://github.com/broxus/ton-labs-executor.git#8cfdfbac2af31d40bf3926b01aeba7ca5d75c214" dependencies = [ "anyhow", "log", @@ -2151,7 +2208,7 @@ dependencies = [ [[package]] name = "ton_types" version = "1.10.2" -source = "git+https://github.com/broxus/ton-labs-types.git#3324562d7ff1ebec66d996128573966c1b53862b" +source = "git+https://github.com/broxus/ton-labs-types.git#8556b60547a20f16d50abcab084479d0c9db3756" dependencies = [ "anyhow", "base64 0.13.1", @@ -2173,7 +2230,7 @@ dependencies = [ [[package]] name = "ton_vm" version = "1.8.29" -source = "git+https://github.com/broxus/ton-labs-vm.git#41a85bcde18bc524c18448ffc6aea35aa383c54a" +source = "git+https://github.com/broxus/ton-labs-vm.git#e693bd7f7a13624503801690a0b70ea5f9b930bd" dependencies = [ "anyhow", "ed25519", diff --git a/Cargo.toml b/Cargo.toml index c48ae21..4bbd586 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nekoton-python" -version = "0.1.23" +version = "0.1.24" edition = "2021" [lib] @@ -17,6 +17,7 @@ hex = "0.4" hmac = "0.11" log = "0.4" num-bigint = "0.4" +num-traits = "0.2" once_cell = "1.17" parking_lot = "0.12" pbkdf2 = "0.9" @@ -35,9 +36,12 @@ tiny-bip39 = { git = "https://github.com/broxus/tiny-bip39.git", default-feature tiny-hderive = { git = "https://github.com/broxus/tiny-hderive.git" } ton_abi = { git = "https://github.com/broxus/ton-labs-abi.git" } -ton_block = { git = "https://github.com/broxus/ton-labs-block.git", features = ["venom"] } +ton_block = { git = "https://github.com/broxus/ton-labs-block.git", features = [ + "venom", +] } ton_types = { git = "https://github.com/broxus/ton-labs-types.git" } ton_executor = { git = "https://github.com/broxus/ton-labs-executor.git" } +ton_vm = { git = "https://github.com/broxus/ton-labs-vm.git" } everscale-asm = { git = "https://github.com/broxus/everscale-asm.git", rev = "bbd284a72676300c89ab074bd39cd91fde21d597" } everscale-types = { version = "0.1.2", default-features = false } diff --git a/README.md b/README.md index 02f3ffd..df86c2d 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,3 @@ -

- - Logo - -

- # nekoton-python   [![Latest Version]][pypi.org] Python bindings for Nekoton @@ -35,7 +29,7 @@ giver_abi = nt.ContractAbi("""{ "events": [] }""") -send_grams = giver_abi.get_function("sendGrams") +send_grams = giver_abi.function("sendGrams") assert send_grams is not None diff --git a/docs/src/pages/guides/installation-and-quick-start.md b/docs/src/pages/guides/installation-and-quick-start.md index f9e054d..6b8f4a1 100644 --- a/docs/src/pages/guides/installation-and-quick-start.md +++ b/docs/src/pages/guides/installation-and-quick-start.md @@ -46,7 +46,7 @@ giver_abi = nt.ContractAbi("""{ "events": [] }""") -send_grams = giver_abi.get_function("sendGrams") +send_grams = giver_abi.function("sendGrams") assert send_grams is not None diff --git a/docs/src/pages/guides/working-with-abi.md b/docs/src/pages/guides/working-with-abi.md index ca373b5..6b321cb 100644 --- a/docs/src/pages/guides/working-with-abi.md +++ b/docs/src/pages/guides/working-with-abi.md @@ -143,10 +143,10 @@ public_key, data = abi.decode_init_data(cell) ### Searching for Function ABI -The `get_function` method of the `ContractAbi` class searches for a function ABI by its name. It returns `FunctionAbi` objects, or `None` if no function with the specified name exists. +The `function` method of the `ContractAbi` class searches for a function ABI by its name. It returns `FunctionAbi` objects, or `None` if no function with the specified name exists. ```python -function_abi = abi.get_function("computeSmth") +function_abi = abi.function("computeSmth") ``` ### Encoding Initial Contract Data @@ -174,7 +174,7 @@ They do not require user interaction and can be called without any parameters or ```python # Initialize the ABI and get the function -function_abi = abi.get_function("getComplexState") +function_abi = abi.function("getComplexState") # Call the function result = function_abi.call(account_state, input={}) @@ -194,7 +194,7 @@ If the getter requires parameters, they can be provided in the `input` dictionar ```python # Initialize the ABI and get the function -function_abi = abi.get_function("getSecondElementWithPrefix") +function_abi = abi.function("getSecondElementWithPrefix") # Call the function with parameters result = function_abi.call(account_state, input={"prefix": "foo"}) @@ -220,7 +220,7 @@ When a responsible method is called on-chain, it returns the result in an outgoi ```python # Initialize the ABI and get the function -function_abi = abi.get_function("computeSmth") +function_abi = abi.function("computeSmth") # Call the function with parameters result = function_abi.call(account_state, input={"offset": 999, "answerId": 42}) @@ -243,7 +243,7 @@ The `encode_external_message` method is utilized to prepare an external message ```python # Initialize the ABI and get the function -function_abi = abi.get_function("setVariableExternal") +function_abi = abi.function("setVariableExternal") # Define the input parameters input_params = {"someParam": 66} @@ -279,7 +279,7 @@ The `encode_internal_message` method is employed to prepare an internal message ```python # Initialize the ABI and get the function -function_abi = abi.get_function("setVariable") +function_abi = abi.function("setVariable") # Define the input parameters input_params = {"someParam": 1337} @@ -386,7 +386,7 @@ They take a `Cell` object and an optional boolean value as arguments, and return ```python # Setting up the ABI for a specific function. -function_abi = abi.get_function("setVariable") +function_abi = abi.function("setVariable") # Decoding a message body as an input using `decode_input` method from `function_abi` object. message_body_cell = nt.Cell.decode("te6ccgEBAQEAFgAAKDja0OwAAAAAAAAAAAAAAAAAAAU5") @@ -404,7 +404,7 @@ print(input_data) ```python # Setting up the ABI for a specific function. -function_abi = abi.get_function("setVariable") +function_abi = abi.function("setVariable") # Decoding a message body as an output using `decode_output` method from `function_abi` object. message_body_cell = nt.Cell.decode("te6ccgEBAQEAFgAAKDja0OwAAAAAAAAAAAAAAAAAAAU5") @@ -437,12 +437,12 @@ The `EventAbi` class is used to interact with the events defined in the smart co ### Searching for Event ABI -The `get_event` method of the `ContractAbi` class searches for an event ABI by its name. +The `event` method of the `ContractAbi` class searches for an event ABI by its name. It returns `EventAbi` objects, or `None` if no event with the specified name exists. ```python -# Searching for an event ABI by its name using `get_event` method of `abi` object. -event_abi = abi.get_event("StateChanged") +# Searching for an event ABI by its name using `event` method of `abi` object. +event_abi = abi.event("StateChanged") print(event_abi) ``` @@ -458,8 +458,8 @@ print(event_abi) The `decode_message` and `decode_message_body` methods decode event data from a message or a message body. They take a `Message` or `Cell` object as an argument respectively, and return a dictionary with the decoded data. ```python -# Firstly, we retrieve the ABI for a specific event by calling the `get_event` method. -event_abi = abi.get_event("StateChanged") +# Firstly, we retrieve the ABI for a specific event by calling the `event` method. +event_abi = abi.event("StateChanged") # Here, we decode two different message bodies (as 'Cell' objects) to extract the data they contain. message_body = nt.Cell.decode("te6ccgEBAgEAEQABEFM5yKUAAAFRAQAIdGVzdA==") diff --git a/examples/giver.py b/examples/giver.py index 58d96c1..00091a6 100644 --- a/examples/giver.py +++ b/examples/giver.py @@ -13,8 +13,7 @@ "events": [] }""") -send_grams = giver_abi.get_function("sendGrams") -assert send_grams is not None +send_grams = giver_abi.function("sendGrams") class Giver: @@ -34,7 +33,7 @@ async def give(self, target: nt.Address, amount: nt.Tokens): "dest": target, "amount": amount, }, - public_key=None + public_key=None, ).without_signature() # Send external message diff --git a/examples/wallet.py b/examples/wallet.py index 414827d..5caaa60 100644 --- a/examples/wallet.py +++ b/examples/wallet.py @@ -20,16 +20,18 @@ "events": [] }""") -send_transaction = wallet_abi.get_function("sendTransaction") -assert send_transaction is not None +send_transaction = wallet_abi.function("sendTransaction") wallet_code = nt.Cell.decode( - 'te6cckEBBgEA/AABFP8A9KQT9LzyyAsBAgEgAgMABNIwAubycdcBAcAA8nqDCNcY7UTQgwfXAdcLP8j4KM8WI88WyfkAA3HXAQHDAJqDB9cBURO68uBk3oBA1wGAINcBgCDXAVQWdfkQ8qj4I7vyeWa++COBBwiggQPoqFIgvLHydAIgghBM7mRsuuMPAcjL/8s/ye1UBAUAmDAC10zQ+kCDBtcBcdcBeNcB10z4AHCAEASqAhSxyMsFUAXPFlAD+gLLaSLQIc8xIddJoIQJuZgzcAHLAFjPFpcwcQHLABLM4skB+wAAPoIQFp4+EbqOEfgAApMg10qXeNcB1AL7AOjRkzLyPOI+zYS/') + "te6cckEBBgEA/AABFP8A9KQT9LzyyAsBAgEgAgMABNIwAubycdcBAcAA8nqDCNcY7UTQgwfXAdcLP8j4KM8WI88WyfkAA3HXAQHDAJqDB9cBURO68uBk3oBA1wGAINcBgCDXAVQWdfkQ8qj4I7vyeWa++COBBwiggQPoqFIgvLHydAIgghBM7mRsuuMPAcjL/8s/ye1UBAUAmDAC10zQ+kCDBtcBcdcBeNcB10z4AHCAEASqAhSxyMsFUAXPFlAD+gLLaSLQIc8xIddJoIQJuZgzcAHLAFjPFpcwcQHLABLM4skB+wAAPoIQFp4+EbqOEfgAApMg10qXeNcB1AL7AOjRkzLyPOI+zYS/" +) class EverWallet: @classmethod - def compute_address(cls, public_key: nt.PublicKey, workchain: int = 0) -> nt.Address: + def compute_address( + cls, public_key: nt.PublicKey, workchain: int = 0 + ) -> nt.Address: return cls.compute_state_init(public_key).compute_address(workchain) @staticmethod @@ -39,7 +41,9 @@ def compute_state_init(public_key: nt.PublicKey) -> nt.StateInit: data_builder.store_u64(0) return nt.StateInit(wallet_code, data_builder.build()) - def __init__(self, transport: nt.Transport, keypair: nt.KeyPair, workchain: int = 0): + def __init__( + self, transport: nt.Transport, keypair: nt.KeyPair, workchain: int = 0 + ): state_init = self.compute_state_init(keypair.public_key) self._initialized = False @@ -52,10 +56,12 @@ def __init__(self, transport: nt.Transport, keypair: nt.KeyPair, workchain: int def address(self) -> nt.Address: return self._address - async def send(self, dst: nt.Address, value: nt.Tokens, payload: nt.Cell, bounce: bool = False) -> nt.Transaction: + async def send( + self, dst: nt.Address, value: nt.Tokens, payload: nt.Cell, bounce: bool = False + ) -> nt.Transaction: state_init = await self.__get_state_init() - signature_id = await self._transport.get_signature_id() + context = await self._transport.get_signature_context() external_message = send_transaction.encode_external_message( self._address, @@ -64,11 +70,11 @@ async def send(self, dst: nt.Address, value: nt.Tokens, payload: nt.Cell, bounce "value": value, "bounce": bounce, "flags": 3, - "payload": payload + "payload": payload, }, public_key=self._keypair.public_key, - state_init=state_init - ).sign(self._keypair, signature_id) + state_init=state_init, + ).sign(self._keypair, context) tx = await self._transport.send_external_message(external_message) if tx is None: @@ -90,7 +96,10 @@ async def __get_state_init(self) -> Optional[nt.StateInit]: return None account_state = await self.get_account_state() - if account_state is not None and account_state.status == nt.AccountStatus.Active: + if ( + account_state is not None + and account_state.status == nt.AccountStatus.Active + ): self._initialized = True return None else: diff --git a/python/nekoton/__init__.py b/python/nekoton/__init__.py index ac5e40f..0628ab5 100644 --- a/python/nekoton/__init__.py +++ b/python/nekoton/__init__.py @@ -1,4 +1,3 @@ +from . import contracts as contracts +from . import gql as gql from .nekoton import * - -from . import gql -from . import contracts diff --git a/python/nekoton/__main__.py b/python/nekoton/__main__.py index 06e654b..ffe29fb 100644 --- a/python/nekoton/__main__.py +++ b/python/nekoton/__main__.py @@ -1,6 +1,5 @@ -import os import argparse - +import os from typing import Optional from . import generator @@ -21,4 +20,5 @@ if contract_name is None: contract_name = os.path.basename(args.filename).removesuffix(".abi.json") + assert contract_name is not None print(generator.generate(contract_name, abi)) diff --git a/python/nekoton/contracts/__init__.py b/python/nekoton/contracts/__init__.py index 705d999..b97e54d 100644 --- a/python/nekoton/contracts/__init__.py +++ b/python/nekoton/contracts/__init__.py @@ -1,6 +1,6 @@ -from .base import IGiver -from .giver_v1 import GiverV1 -from .giver_v2 import GiverV2 -from .ever_wallet import EverWallet -from .walletv3 import WalletV3 -from .highload_wallet_v2 import HighloadWalletV2 +from .base import IGiver as IGiver +from .ever_wallet import EverWallet as EverWallet +from .giver_v1 import GiverV1 as GiverV1 +from .giver_v2 import GiverV2 as GiverV2 +from .highload_wallet_v2 import HighloadWalletV2 as HighloadWalletV2 +from .walletv3 import WalletV3 as WalletV3 diff --git a/python/nekoton/contracts/base.py b/python/nekoton/contracts/base.py index 48d26fe..3245613 100644 --- a/python/nekoton/contracts/base.py +++ b/python/nekoton/contracts/base.py @@ -1,4 +1,4 @@ -import nekoton as _nt +import nekoton.nekoton as _nt class IGiver: diff --git a/python/nekoton/contracts/ever_wallet.py b/python/nekoton/contracts/ever_wallet.py index c6072f4..0d0c0bd 100644 --- a/python/nekoton/contracts/ever_wallet.py +++ b/python/nekoton/contracts/ever_wallet.py @@ -1,8 +1,8 @@ -from typing import Optional, List +from typing import List, Optional -from . import IGiver -import nekoton as _nt +import nekoton.nekoton as _nt +from .base import IGiver _wallet_abi = _nt.ContractAbi("""{ "ABI version": 2, @@ -79,13 +79,11 @@ } """) -_send_transaction = _wallet_abi.get_function("sendTransaction") -assert _send_transaction is not None +_send_transaction = _wallet_abi.function("sendTransaction") -_send_transaction_raw = list() +_send_transaction_raw: List[_nt.FunctionAbi] = list() for i in range(4): - abi = _wallet_abi.get_function(f"sendTransactionRaw{i}") - assert abi is not None + abi = _wallet_abi.function(f"sendTransactionRaw{i}") _send_transaction_raw.append(abi) _wallet_code = _nt.Cell.decode( diff --git a/python/nekoton/contracts/giver_v1.py b/python/nekoton/contracts/giver_v1.py index 8a57965..c9f4ac5 100644 --- a/python/nekoton/contracts/giver_v1.py +++ b/python/nekoton/contracts/giver_v1.py @@ -1,7 +1,8 @@ from __future__ import annotations -from . import IGiver -import nekoton as _nt +import nekoton.nekoton as _nt + +from .base import IGiver _giver_v1_abi = _nt.ContractAbi("""{ "ABI version": 1, @@ -20,8 +21,8 @@ "events": [] }""") -_giver_v1_constructor = _giver_v1_abi.get_function("constructor") -_giver_v1_send_grams = _giver_v1_abi.get_function("sendGrams") +_giver_v1_constructor = _giver_v1_abi.function("constructor") +_giver_v1_send_grams = _giver_v1_abi.function("sendGrams") _giver_v1_tvc = "te6ccgECJQEABaMAAgE0BgEBAcACAgPPIAUDAQHeBAAD0CAAQdgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAIo/wAgwAH0pCBYkvSg4YrtU1gw9KATBwEK9KQg9KEIAgPNQBAJAgHODQoCASAMCwAHDDbMIAAdPAZIbzyvCEhcHHwCl8CgAgEgDw4AASAA1T++wFkZWNvZGVfYWRkciD6QDL6QiBvECByuiFzurHy4H0hbxFu8uB9yHTPCwIibxLPCgcibxMicrqWI28TIs4ynyGBAQAi10mhz0AyICLOMuL+/AFkZWNvZGVfYWRkcjAhydAlVUFfBdswgAgEgEhEAK6T/fYCzsrovsTC2MLcxsvwTt4htmEAApaV/fYCwsa+6OTC3ObMyuWQ5Z6ARZ4UAOOegfBRnixJnixH9ATjnoDh9ATh9AUAgZ6B8EeeFj7lnoBBkkX2Af3+AsLGvujkwtzmzMrkvsrcyL4LAAgEgGhQB4P/+/QFtYWluX2V4dGVybmFsIY5Z/vwBZ2V0X3NyY19hZGRyINAg0wAycL2OGv79AWdldF9zcmNfYWRkcjBwyMnQVRFfAtsw4CBy1yExINMAMiH6QDP+/QFnZXRfc3JjX2FkZHIxISFVMV8E2zDYMSEVAfiOdf7+AWdldF9tc2dfcHVia2V5IMcCjhb+/wFnZXRfbXNnX3B1YmtleTFwMdsw4NUgxwGOF/7/AWdldF9tc2dfcHVia2V5MnAxMdsw4CCBAgDXIdcL/yL5ASIi+RDyqP7/AWdldF9tc2dfcHVia2V5MyADXwPbMNgixwKzFgHMlCLUMTPeJCIijjj++QFzdG9yZV9zaWdvACFvjCJvjCNvjO1HIW+M7UTQ9AVvjCDtV/79AXN0b3JlX3NpZ19lbmRfBdgixwGOE/78AW1zZ19pc19lbXB0eV8G2zDgItMfNCPTPzUgFwF2joDYji/+/gFtYWluX2V4dGVybmFsMiQiVXFfCPFAAf7+AW1haW5fZXh0ZXJuYWwzXwjbMOCAfPLwXwgYAf7++wFyZXBsYXlfcHJvdHBwcO1E0CD0BDI0IIEAgNdFmiDTPzIzINM/MjKWgggbd0Ay4iIluSX4I4ED6KgkoLmwjinIJAH0ACXPCz8izws/Ic8WIMntVP78AXJlcGxheV9wcm90Mn8GXwbbMOD+/AFyZXBsYXlfcHJvdDNwBV8FGQAE2zACASAcGwAPvOP3EDmG2YQCASAeHQCJuyXMvJ+ADwINM/MPAi/vwBcHVzaHBkYzd0b2M07UTQ9AHI7UdvEgH0ACHPFiDJ7VT+/QFwdXNocGRjN3RvYzQwXwLbMIAgEgIh8BCbiJACdQIAH+/v0BY29uc3RyX3Byb3RfMHBwgggbd0DtRNAg9AQyNCCBAIDXRY4UINI/MjMg0j8yMiBx10WUgHvy8N7eyCQB9AAjzws/Is8LP3HPQSHPFiDJ7VT+/QFjb25zdHJfcHJvdF8xXwX4ADDwIf78AXB1c2hwZGM3dG9jNO1E0PQByCEARO1HbxIB9AAhzxYgye1U/v0BcHVzaHBkYzd0b2M0MF8C2zAB4tz+/QFtYWluX2ludGVybmFsIY5Z/vwBZ2V0X3NyY19hZGRyINAg0wAycL2OGv79AWdldF9zcmNfYWRkcjBwyMnQVRFfAtsw4CBy1yExINMAMiH6QDP+/QFnZXRfc3JjX2FkZHIxISFVMV8E2zDYJCFwIwHqjjj++QFzdG9yZV9zaWdvACFvjCJvjCNvjO1HIW+M7UTQ9AVvjCDtV/79AXN0b3JlX3NpZ19lbmRfBdgixwCOHCFwuo4SIoIQXH7iB1VRXwbxQAFfBtsw4F8G2zDg/v4BbWFpbl9pbnRlcm5hbDEi0x80InG6JAA2niCAI1VhXwfxQAFfB9sw4CMhVWFfB/FAAV8H" @@ -55,6 +56,10 @@ async def deploy( raise RuntimeError("Message expired") await transport.trace_transaction(tx).wait() + state = await transport.get_account_state(address) + if state is None: + raise RuntimeError("Account didn't receive funds") + # Deploy account if state.status == _nt.AccountStatus.Active: return GiverV1(transport, workchain) @@ -63,6 +68,9 @@ async def deploy( elif ( state.status == _nt.AccountStatus.Uninit and state.balance < initial_balance ): + if other_giver is None: + raise RuntimeError("Account does not have enough balance") + tx = await other_giver.give(address, initial_balance) if tx is None: raise RuntimeError("Message expired") diff --git a/python/nekoton/contracts/giver_v2.py b/python/nekoton/contracts/giver_v2.py index 99dca02..9ae9617 100644 --- a/python/nekoton/contracts/giver_v2.py +++ b/python/nekoton/contracts/giver_v2.py @@ -1,7 +1,8 @@ from __future__ import annotations -from . import IGiver -import nekoton as _nt +import nekoton.nekoton as _nt + +from .base import IGiver _giver_v2_abi = _nt.ContractAbi("""{ "ABI version": 2, @@ -22,8 +23,8 @@ "events": [] }""") -_giver_v2_constructor = _giver_v2_abi.get_function("constructor") -_giver_v2_send_grams = _giver_v2_abi.get_function("sendTransaction") +_giver_v2_constructor = _giver_v2_abi.function("constructor") +_giver_v2_send_grams = _giver_v2_abi.function("sendTransaction") _giver_v2_tvc = "te6ccgECIAEAA6YAAgE0BgEBAcACAgPPIAUDAQHeBAAD0CAAQdgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAIm/wD0pCAiwAGS9KDhiu1TWDD0oQkHAQr0pCD0oQgAAAIBIA0KAQL/CwH+fyHtRNAg10nCAZ/T/9MA9AX4an/4Yfhm+GKOG/QFbfhqcAGAQPQO8r3XC//4YnD4Y3D4Zn/4YeLTAAGOEoECANcYIPkBWPhCIPhl+RDyqN4j+EUgbpIwcN74Qrry4GUh0z/THzQg+CO88rki+QAg+EqBAQD0DiCRMd7y0Gb4AAwANiD4SiPIyz9ZgQEA9EP4al8E0x8B8AH4R27yfAIBIBQOAgFYEg8BCbjomPxQEAHW+EFujhLtRNDT/9MA9AX4an/4Yfhm+GLe0XBtbwL4SoEBAPSGlQHXCz9/k3BwcOKRII4yXzPIIs8L/yHPCz8xMQFvIiGkA1mAIPRDbwI0IvhKgQEA9HyVAdcLP3+TcHBw4gI1MzHoXwMhwP8RAJiOLiPQ0wH6QDAxyM+HIM6NBAAAAAAAAAAAAAAAAA90TH4ozxYhbyICyx/0AMlx+wDeMMD/jhL4QsjL//hGzwsA+EoB9ADJ7VTef/hnAQm5Fqvn8BMAtvhBbo427UTQINdJwgGf0//TAPQF+Gp/+GH4Zvhijhv0BW34anABgED0DvK91wv/+GJw+GNw+GZ/+GHi3vhG8nNx+GbR+AD4QsjL//hGzwsA+EoB9ADJ7VR/+GcCASAYFQEJuxXvk1gWAbb4QW6OEu1E0NP/0wD0Bfhqf/hh+Gb4Yt76QNcNf5XU0dDTf9/XDACV1NHQ0gDf0VRxIMjPhYDKAHPPQM4B+gKAa89AyXP7APhKgQEA9IaVAdcLP3+TcHBw4pEgFwCEjigh+CO7myL4SoEBAPRbMPhq3iL4SoEBAPR8lQHXCz9/k3BwcOICNTMx6F8G+ELIy//4Rs8LAPhKAfQAye1Uf/hnAgEgGxkBCbjkYYdQGgC++EFujhLtRNDT/9MA9AX4an/4Yfhm+GLe1NH4RSBukjBw3vhCuvLgZfgA+ELIy//4Rs8LAPhKAfQAye1U+A8g+wQg0O0e7VPwAjD4QsjL//hGzwsA+EoB9ADJ7VR/+GcCAtoeHAEBSB0ALPhCyMv/+EbPCwD4SgH0AMntVPgP8gABAUgfAFhwItDWAjHSADDcIccA3CHXDR/yvFMR3cEEIoIQ/////byx8nwB8AH4R27yfA==" @@ -74,14 +75,21 @@ async def deploy( raise RuntimeError("Message expired") await transport.trace_transaction(tx).wait() + state = await transport.get_account_state(address) + if state is None: + raise RuntimeError("Account didn't receive funds") + # Deploy account if state.status == _nt.AccountStatus.Active: - return GiverV2(transport, workchain) + return GiverV2(transport, keypair, workchain) elif state.status == _nt.AccountStatus.Frozen: raise RuntimeError("Giver account is frozen") elif ( state.status == _nt.AccountStatus.Uninit and state.balance < initial_balance ): + if other_giver is None: + raise RuntimeError("Account does not have enough balance") + tx = await other_giver.give(address, initial_balance) if tx is None: raise RuntimeError("Message expired") @@ -99,7 +107,7 @@ async def deploy( raise RuntimeError("Message expired") await transport.trace_transaction(tx).wait() - return GiverV2(transport, workchain) + return GiverV2(transport, keypair, workchain) def __init__( self, transport: _nt.Transport, keypair: _nt.KeyPair, workchain: int = 0 diff --git a/python/nekoton/contracts/highload_wallet_v2.py b/python/nekoton/contracts/highload_wallet_v2.py index a13431d..624d9d9 100644 --- a/python/nekoton/contracts/highload_wallet_v2.py +++ b/python/nekoton/contracts/highload_wallet_v2.py @@ -1,7 +1,8 @@ -from typing import Optional, List +from typing import List, Optional -from . import IGiver -import nekoton as _nt +import nekoton.nekoton as _nt + +from .base import IGiver _wallet_code = _nt.Cell.decode( "te6ccgEBCQEA5QABFP8A9KQT9LzyyAsBAgEgBAIB6vKDCNcYINMf0z/4I6ofUyC58mPtRNDTH9M/0//0BNFTYIBA9A5voTHyYFFzuvKiB/kBVBCH+RDyowL0BNH4AH+OFiGAEPR4b6UgmALTB9QwAfsAkTLiAbPmW4MlochANIBA9EOK5jHIEssfE8s/y//0AMntVAMANCCAQPSWb6UyURCUMFMDud4gkzM2AZIyMOKzAgFICAUCASAHBgBBvl+XaiaGmPmOmf6f+Y+gJoqRBAIHoHN9CYyS2/yV3R8UABe9nOdqJoaa+Y64X/wABNAw" @@ -9,7 +10,7 @@ _default_wallet_id = 0x00000000 _default_ttl = 60 -_messages_abi = [ +_messages_abi: list[tuple[str, _nt.AbiParam]] = [ ( "messages", _nt.AbiMap( @@ -172,10 +173,7 @@ async def __get_state_init(self) -> Optional[_nt.StateInit]: return None account_state = await self.get_account_state() - if ( - account_state is not None - and account_state.status == _nt.AccountStatus.Active - ): + if account_state is not None and account_state.state_init is not None: if account_state.state_init.data is None: raise RuntimeError("Account state does not contain data") diff --git a/python/nekoton/contracts/walletv3.py b/python/nekoton/contracts/walletv3.py index 491c304..2f511f9 100644 --- a/python/nekoton/contracts/walletv3.py +++ b/python/nekoton/contracts/walletv3.py @@ -1,7 +1,8 @@ -from typing import Optional, List +from typing import List, Optional -from . import IGiver -import nekoton as _nt +import nekoton.nekoton as _nt + +from .base import IGiver _wallet_code = _nt.Cell.decode( "te6ccgEBAQEAcQAA3v8AIN0gggFMl7ohggEznLqxn3Gw7UTQ0x/THzHXC//jBOCk8mCDCNcYINMf0x/TH/gjE7vyY+1E0NMf0x/T/9FRMrryoVFEuvKiBPkBVBBV+RDyo/gAkyDXSpbTB9QC+wDo0QGkyMsfyx/L/8ntVA==" @@ -47,7 +48,6 @@ def __init__( ): state_init = self.compute_state_init(keypair.public_key, wallet_id) - self._initialized = False self._wallet_id = wallet_id self._transport = transport self._keypair = keypair @@ -150,14 +150,8 @@ async def get_balance(self) -> _nt.Tokens: return state.balance async def __get_seqno_and_state_init(self) -> tuple[int, Optional[_nt.StateInit]]: - if self._initialized: - return None - account_state = await self.get_account_state() - if ( - account_state is not None - and account_state.status == _nt.AccountStatus.Active - ): + if account_state is not None and account_state.state_init is not None: if account_state.state_init.data is None: raise RuntimeError("Account state does not contain data") @@ -167,7 +161,6 @@ async def __get_seqno_and_state_init(self) -> tuple[int, Optional[_nt.StateInit] # NOTE: Update wallet_id just in case self._wallet_id = data.load_u32() - self._initialized = True return seqno, None else: return 0, self._state_init diff --git a/python/nekoton/generator/__init__.py b/python/nekoton/generator/__init__.py index fe55ca2..2a6d40e 100644 --- a/python/nekoton/generator/__init__.py +++ b/python/nekoton/generator/__init__.py @@ -1,6 +1,6 @@ import json import re -from typing import Optional, List, Dict +from typing import Dict, List, Optional from nekoton import ContractAbi @@ -16,7 +16,9 @@ def __init__(self, name: str, decl: str): class InternalFunction: - def __init__(self, abi_name: str, inputs_ty: str, outputs_ty: str, struct: InternalStruct): + def __init__( + self, abi_name: str, inputs_ty: str, outputs_ty: str, struct: InternalStruct + ): self.abi_name = abi_name self.inputs_ty = inputs_ty self.outputs_ty = outputs_ty @@ -36,7 +38,7 @@ def optional_type(ty: Optional[str]) -> str: self.head += "):" self.body = None - def with_body(self, body) -> 'MethodBuilder': + def with_body(self, body) -> "MethodBuilder": self.body = body return self @@ -56,15 +58,17 @@ def __init__(self, name: str) -> None: self.fields = [] self.methods = [] - def with_base(self, base: str) -> 'ClassBuilder': + def with_base(self, base: str) -> "ClassBuilder": self.derive.append(base) return self - def with_field(self, name: str, ty: str) -> 'ClassBuilder': + def with_field(self, name: str, ty: str) -> "ClassBuilder": self.fields.append((name, ty)) return self - def with_method(self, method: MethodBuilder, attributes: List[str] = []) -> 'ClassBuilder': + def with_method( + self, method: MethodBuilder, attributes: List[str] = [] + ) -> "ClassBuilder": self.methods.append((method, attributes)) return self @@ -140,7 +144,10 @@ def generate_abi_class(self, name: str, abi: str) -> str: abi_object = class_name.upper() + "_ABI" abi_object_decl = '%s = %s(r"""%s""")\n\n' % ( - abi_object, self._from_nekoton.import_ty("ContractAbi"), json.dumps(parsed_abi)) + abi_object, + self._from_nekoton.import_ty("ContractAbi"), + json.dumps(parsed_abi), + ) result = ClassBuilder(class_name + "Abi") function_classes_decl = "" @@ -149,9 +156,9 @@ def generate_abi_class(self, name: str, abi: str) -> str: function_classes_decl += f"{function_class.struct.decl}\n\n" member_name = depascalize(pascalize(function_class.abi_name)) - method = MethodBuilder(member_name, {}, function_class.struct.name).with_body( - f'return {abi_object}.get_function("{function_class.abi_name}")' - ) + method = MethodBuilder( + member_name, {}, function_class.struct.name + ).with_body(f'return {abi_object}.function("{function_class.abi_name}")') result.with_method(method, ["@staticmethod"]) class_decl = result.generate() @@ -171,68 +178,93 @@ def generate_function(self, base_class: str, function: dict) -> InternalFunction outputs = function["outputs"] name_struct = pascalize(name) - inputs_ty = self.generate_tuple(inputs, name_struct + "Input", cache_signature=False) - outputs_ty = self.generate_tuple(outputs, name_struct + "Output", cache_signature=False) + inputs_ty = self.generate_tuple( + inputs, name_struct + "Input", cache_signature=False + ) + outputs_ty = self.generate_tuple( + outputs, name_struct + "Output", cache_signature=False + ) function_ty = base_class + pascalize(name) + "Function" function_call_ty = function_ty + "Call" function_call_base_ty = self._from_nekoton.import_ty("FunctionCall") - function_call_input_method = MethodBuilder("input", {"self": None}, inputs_ty).with_body( - f"return {function_call_base_ty}.input" - ) - function_call_output_method = MethodBuilder("output", {"self": None}, outputs_ty).with_body( - f"return {function_call_base_ty}.output" + function_call_input_method = MethodBuilder( + "input", {"self": None}, inputs_ty + ).with_body(f"return {function_call_base_ty}.input") + function_call_output_method = MethodBuilder( + "output", {"self": None}, outputs_ty + ).with_body(f"return {function_call_base_ty}.output") + function_call = ( + ClassBuilder(function_ty + "Call") + .with_base(function_call_base_ty) + .with_method(function_call_input_method, ["@property"]) + .with_method(function_call_output_method, ["@property"]) ) - function_call = ClassBuilder(function_ty + "Call").with_base(function_call_base_ty)\ - .with_method(function_call_input_method, ['@property'])\ - .with_method(function_call_output_method, ['@property']) - self._internal_structs_decl += f"{function_call.generate()}\n\n" + self._internal_structs_decl += f"{function_call.generate()}\n\n" function_abi_ty = self._from_nekoton.import_ty("FunctionAbi") function_with_args_ty = self._from_nekoton.import_ty("FunctionAbiWithArgs") - with_args_method = MethodBuilder("with_args", { - "self": None, - "args": inputs_ty, - }, function_with_args_ty).with_body(f"return {function_abi_ty}.with_args(self, args)") - - decode_transaction = MethodBuilder("decode_transaction", { - "self": None, - "transaction": self._from_nekoton.import_ty("Transaction") - }, function_call_ty).with_body(f"return {function_abi_ty}.decode_transaction(transaction)") - - decode_input_method = MethodBuilder("decode_input", { - "self": None, - "message_body": self._from_nekoton.import_ty("Cell"), - "internal": "bool", - "allow_partial": f'{self._from_typing.import_ty("Optional")}[bool] = None', - }, inputs_ty).with_body( + with_args_method = MethodBuilder( + "with_args", + { + "self": None, + "args": inputs_ty, + }, + function_with_args_ty, + ).with_body(f"return {function_abi_ty}.with_args(self, args)") + + decode_transaction = MethodBuilder( + "decode_transaction", + {"self": None, "transaction": self._from_nekoton.import_ty("Transaction")}, + function_call_ty, + ).with_body(f"return {function_abi_ty}.decode_transaction(transaction)") + + decode_input_method = MethodBuilder( + "decode_input", + { + "self": None, + "message_body": self._from_nekoton.import_ty("Cell"), + "internal": "bool", + "allow_partial": f"{self._from_typing.import_ty('Optional')}[bool] = None", + }, + inputs_ty, + ).with_body( f"return {function_abi_ty}.decode_input(self, message_body, internal, allow_partial)" ) - decode_output_method = MethodBuilder("decode_output", { - "self": None, - "message_body": self._from_nekoton.import_ty("Cell"), - "allow_partial": f'{self._from_typing.import_ty("Optional")}[bool] = None', - }, outputs_ty).with_body( + decode_output_method = MethodBuilder( + "decode_output", + { + "self": None, + "message_body": self._from_nekoton.import_ty("Cell"), + "allow_partial": f"{self._from_typing.import_ty('Optional')}[bool] = None", + }, + outputs_ty, + ).with_body( f"return {function_abi_ty}.decode_output(self, message_body, allow_partial)" ) - result = ClassBuilder(function_ty).with_base(function_abi_ty)\ - .with_method(with_args_method)\ - .with_method(decode_input_method)\ - .with_method(decode_output_method)\ - .with_method(decode_transaction)\ + result = ( + ClassBuilder(function_ty) + .with_base(function_abi_ty) + .with_method(with_args_method) + .with_method(decode_input_method) + .with_method(decode_output_method) + .with_method(decode_transaction) .generate() - return InternalFunction(name, inputs_ty, outputs_ty, InternalStruct(function_ty, result)) + ) + return InternalFunction( + name, inputs_ty, outputs_ty, InternalStruct(function_ty, result) + ) def generate_type(self, ty: str, components: Optional[List[dict]]) -> str: if ty == "": raise InvalidTypeError(ty) elif ty.endswith("]"): - len_idx = ty.rfind('[') + len_idx = ty.rfind("[") if len_idx == -1: raise InvalidTypeError(ty) value_ty = self.generate_type(ty[:len_idx], components) @@ -246,13 +278,13 @@ def generate_type(self, ty: str, components: Optional[List[dict]]) -> str: elif ty.startswith("optional("): if not ty.endswith(")"): raise InvalidTypeError(ty) - value_ty = self.generate_type(ty[ty.find("(") + 1:-1], components) + value_ty = self.generate_type(ty[ty.find("(") + 1 : -1], components) return "%s[%s]" % (self._from_typing.import_ty("Optional"), value_ty) elif ty.startswith("ref("): if not ty.endswith(")"): raise InvalidTypeError(ty) - return self.generate_type(ty[ty.find("(") + 1:-1], components) + return self.generate_type(ty[ty.find("(") + 1 : -1], components) elif ty.startswith("map("): if not ty.endswith(")"): @@ -264,16 +296,25 @@ def generate_type(self, ty: str, components: Optional[List[dict]]) -> str: raise InvalidTypeError(ty) key_ty = self.generate_type(ty[key_ty_idx:delim_idx], None) - value_ty = self.generate_type(ty[delim_idx + 1:-1], components) + value_ty = self.generate_type(ty[delim_idx + 1 : -1], components) - return "%s[%s, %s]" % (self._from_typing.import_ty("Dict"), key_ty, value_ty) + return "%s[%s, %s]" % ( + self._from_typing.import_ty("Dict"), + key_ty, + value_ty, + ) elif ty == "bool": return ty - elif ty.startswith("int") or ty.startswith("uint") or \ - ty.startswith("varint") or ty.startswith("varuint") or \ - ty.startswith("time") or ty.startswith("expire"): + elif ( + ty.startswith("int") + or ty.startswith("uint") + or ty.startswith("varint") + or ty.startswith("varuint") + or ty.startswith("time") + or ty.startswith("expire") + ): return "int | %s" % self._from_nekoton.import_ty("Tokens") elif ty == "cell": @@ -297,7 +338,9 @@ def generate_type(self, ty: str, components: Optional[List[dict]]) -> str: else: raise InvalidTypeError(ty) - def generate_tuple(self, components: List[dict], name: Optional[str], cache_signature: bool) -> str: + def generate_tuple( + self, components: List[dict], name: Optional[str], cache_signature: bool + ) -> str: type_signature = "" if cache_signature: type_signature = compute_tuple_signature(components) @@ -314,7 +357,9 @@ def generate_tuple(self, components: List[dict], name: Optional[str], cache_sign field_name = component["name"] field_ty = component["type"] field_components = component.get("components") - result.with_field(field_name, self.generate_type(field_ty, field_components)) + result.with_field( + field_name, self.generate_type(field_ty, field_components) + ) if cache_signature: self._internal_structs[type_signature] = name @@ -372,7 +417,7 @@ def privatize(ty: str) -> str: def indent_text(text: str, level: int) -> str: - indent = ' ' * (4 * level) + indent = " " * (4 * level) lines = text.strip().split("\n") return "\n".join(f"{indent}{line}" for line in lines) diff --git a/python/nekoton/gql/__init__.py b/python/nekoton/gql/__init__.py index 6df0932..5bd4a15 100644 --- a/python/nekoton/gql/__init__.py +++ b/python/nekoton/gql/__init__.py @@ -1,10 +1,8 @@ from typing import List as _List -from nekoton import GqlExprPart -from . import filters -from . import msg -from . import tx -from . import acc +from nekoton.nekoton import GqlExprPart + +from . import acc, filters, msg, tx def and_(expressions: str | GqlExprPart | _List[GqlExprPart]) -> GqlExprPart: @@ -13,7 +11,7 @@ def and_(expressions: str | GqlExprPart | _List[GqlExprPart]) -> GqlExprPart: if isinstance(expressions, str): return GqlExprPart(expressions) else: - return GqlExprPart(','.join(map(str, expressions))) + return GqlExprPart(",".join(map(str, expressions))) def or_(expressions: _List[str | GqlExprPart | _List[GqlExprPart]]) -> GqlExprPart: @@ -21,7 +19,7 @@ def or_(expressions: _List[str | GqlExprPart | _List[GqlExprPart]]) -> GqlExprPa for expr in reversed(expressions): element = and_(expr) if last_part is not None: - last_part = '{},OR:{{{}}}'.format(element, last_part) + last_part = GqlExprPart("{},OR:{{{}}}".format(element, last_part)) else: last_part = element diff --git a/python/nekoton/gql/acc.py b/python/nekoton/gql/acc.py index 16c9aa2..66bd2e6 100644 --- a/python/nekoton/gql/acc.py +++ b/python/nekoton/gql/acc.py @@ -1,6 +1,10 @@ -from .filters import IntFilter as _IntFilter, OrderBy as _OrderBy, AddressFilter as _AddressFilter, \ - AccountStatusFilter as _AccountStatusFilter, TokensFilter as _TokensFilter, HashFilter as _HashFilter, \ - IntAsStringFilter as _IntAsStringFilter +from .filters import AccountStatusFilter as _AccountStatusFilter +from .filters import AddressFilter as _AddressFilter +from .filters import HashFilter as _HashFilter +from .filters import IntAsStringFilter as _IntAsStringFilter +from .filters import IntFilter as _IntFilter +from .filters import OrderBy as _OrderBy +from .filters import TokensFilter as _TokensFilter class WorkchainId(_IntFilter, _OrderBy): diff --git a/python/nekoton/gql/filters.py b/python/nekoton/gql/filters.py index 8f5c071..0faee52 100644 --- a/python/nekoton/gql/filters.py +++ b/python/nekoton/gql/filters.py @@ -1,7 +1,13 @@ from typing import Iterable as _Iterable -from nekoton import Address as _Address, Tokens as _Tokens, TransactionType as _TransactionType, MessageType as _MessageType, \ - AccountStatus as _AccountStatus, GqlExprPart +from nekoton.nekoton import AccountStatus as _AccountStatus +from nekoton.nekoton import Address as _Address +from nekoton.nekoton import GqlExprPart +from nekoton.nekoton import MessageType as _MessageType +from nekoton.nekoton import Tokens as _Tokens +from nekoton.nekoton import TransactionType as _TransactionType + +# pyright: reportIncompatibleMethodOverride=false class OrderBy: @@ -21,9 +27,9 @@ def __init__(self, field: str): def _op(self, value: bool) -> GqlExprPart: if value: - return GqlExprPart('{}:{{eq:true}}'.format(self._field)) + return GqlExprPart("{}:{{eq:true}}".format(self._field)) else: - return GqlExprPart('{}:{{eq:false}}'.format(self._field)) + return GqlExprPart("{}:{{eq:false}}".format(self._field)) def __invert__(self) -> GqlExprPart: return self._op(False) @@ -40,16 +46,18 @@ def __init__(self, field: str): self._field = field def any_of(self, values: _Iterable[int]) -> GqlExprPart: - return self._multi_op('in', values) + return self._multi_op("in", values) def not_any_of(self, values: _Iterable[int]) -> GqlExprPart: - return self._multi_op('notIn', values) + return self._multi_op("notIn", values) def _op(self, op: str, value: int) -> GqlExprPart: - return GqlExprPart('{}:{{{}:{}}}'.format(self._field, op, value)) + return GqlExprPart("{}:{{{}:{}}}".format(self._field, op, value)) def _multi_op(self, op: str, values: _Iterable[int]) -> GqlExprPart: - return GqlExprPart('{}:{{{}:[{}]}}'.format(self._field, op, ','.join(map(str, values)))) + return GqlExprPart( + "{}:{{{}:[{}]}}".format(self._field, op, ",".join(map(str, values))) + ) def __eq__(self, other: int) -> GqlExprPart: return self._op("eq", other) @@ -72,10 +80,10 @@ def __init__(self, field: str): self._field = field def any_of(self, values: _Iterable[str]) -> GqlExprPart: - return self._multi_op('in', values) + return self._multi_op("in", values) def not_any_of(self, values: _Iterable[str]) -> GqlExprPart: - return self._multi_op('notIn', values) + return self._multi_op("notIn", values) def _op(self, op: str, value: str) -> GqlExprPart: return GqlExprPart('{}:{{{}:"{}"}}'.format(self._field, op, value)) @@ -83,7 +91,7 @@ def _op(self, op: str, value: str) -> GqlExprPart: def _multi_op(self, op: str, values: _Iterable[str]) -> GqlExprPart: values = '","'.join(values) if not values: - return GqlExprPart('{}:{{{}:[]}}'.format(self._field, op)) + return GqlExprPart("{}:{{{}:[]}}".format(self._field, op)) else: return GqlExprPart('{}:{{{}:["{}"]}}'.format(self._field, op, values)) @@ -105,10 +113,10 @@ def __le__(self, other: str) -> GqlExprPart: class IntAsStringFilter(StringFilter): def any_of(self, values: _Iterable[int | str]) -> GqlExprPart: - return self._multi_op('in', map(str, values)) + return self._multi_op("in", map(str, values)) def not_any_of(self, values: _Iterable[int | str]) -> GqlExprPart: - return self._multi_op('notIn', map(str, values)) + return self._multi_op("notIn", map(str, values)) def __eq__(self, other: int | str) -> GqlExprPart: return self._op("eq", str(other)) @@ -135,10 +143,10 @@ def __convert(value: _Tokens | int | str) -> str: return str(value) def any_of(self, values: _Iterable[_Tokens | int | str]) -> GqlExprPart: - return self._multi_op('in', map(TokensFilter.__convert, values)) + return self._multi_op("in", map(TokensFilter.__convert, values)) def not_any_of(self, values: _Iterable[_Tokens | int | str]) -> GqlExprPart: - return self._multi_op('notIn', map(TokensFilter.__convert, values)) + return self._multi_op("notIn", map(TokensFilter.__convert, values)) def __eq__(self, other: _Tokens | int | str) -> GqlExprPart: return self._op("eq", TokensFilter.__convert(other)) @@ -165,10 +173,10 @@ def __convert(value: bytes | str) -> str: return value def any_of(self, values: _Iterable[bytes | str]) -> GqlExprPart: - return self._multi_op('in', map(HashFilter.__convert, values)) + return self._multi_op("in", map(HashFilter.__convert, values)) def not_any_of(self, values: _Iterable[bytes | str]) -> GqlExprPart: - return self._multi_op('notIn', map(HashFilter.__convert, values)) + return self._multi_op("notIn", map(HashFilter.__convert, values)) def __eq__(self, other: bytes | str) -> GqlExprPart: return self._op("eq", HashFilter.__convert(other)) @@ -188,10 +196,10 @@ def __le__(self, other: bytes | str) -> GqlExprPart: class AddressFilter(StringFilter): def any_of(self, values: _Iterable[_Address | str]) -> GqlExprPart: - return self._multi_op('in', map(str, values)) + return self._multi_op("in", map(str, values)) def not_any_of(self, values: _Iterable[_Address | str]) -> GqlExprPart: - return self._multi_op('notIn', map(str, values)) + return self._multi_op("notIn", map(str, values)) def __eq__(self, other: _Address | str) -> GqlExprPart: return self._op("eq", str(other)) @@ -211,10 +219,10 @@ def __le__(self, other: _Address | str) -> GqlExprPart: class TransactionTypeFilter(IntFilter): def any_of(self, values: _Iterable[_TransactionType | int]) -> GqlExprPart: - return self._multi_op('in', map(int, values)) + return self._multi_op("in", map(int, values)) def not_any_of(self, values: _Iterable[_TransactionType | int]) -> GqlExprPart: - return self._multi_op('notIn', map(int, values)) + return self._multi_op("notIn", map(int, values)) def __eq__(self, other: _TransactionType | int) -> GqlExprPart: return self._op("eq", int(other)) @@ -234,10 +242,10 @@ def __le__(self, other: _TransactionType | int) -> GqlExprPart: class MessageTypeFilter(IntFilter): def any_of(self, values: _Iterable[_MessageType | int]) -> GqlExprPart: - return self._multi_op('in', map(int, values)) + return self._multi_op("in", map(int, values)) def not_any_of(self, values: _Iterable[_MessageType | int]) -> GqlExprPart: - return self._multi_op('notIn', map(int, values)) + return self._multi_op("notIn", map(int, values)) def __eq__(self, other: _MessageType | int) -> GqlExprPart: return self._op("eq", int(other)) @@ -257,10 +265,10 @@ def __le__(self, other: _MessageType | int) -> GqlExprPart: class AccountStatusFilter(IntFilter): def any_of(self, values: _Iterable[_AccountStatus | int]) -> GqlExprPart: - return self._multi_op('in', map(int, values)) + return self._multi_op("in", map(int, values)) def not_any_of(self, values: _Iterable[_AccountStatus | int]) -> GqlExprPart: - return self._multi_op('notIn', map(int, values)) + return self._multi_op("notIn", map(int, values)) def __eq__(self, other: _AccountStatus | int) -> GqlExprPart: return self._op("eq", int(other)) diff --git a/python/nekoton/gql/msg.py b/python/nekoton/gql/msg.py index 34dd020..665f648 100644 --- a/python/nekoton/gql/msg.py +++ b/python/nekoton/gql/msg.py @@ -1,6 +1,11 @@ -from .filters import OrderBy as _OrderBy, IntFilter as _IntFilter, HashFilter as _HashFilter, TokensFilter as _TokensFilter, \ - BoolFilter as _BoolFilter, AddressFilter as _AddressFilter, MessageTypeFilter as _MessageTypeFilter, \ - IntAsStringFilter as _IntAsStringFilter +from .filters import AddressFilter as _AddressFilter +from .filters import BoolFilter as _BoolFilter +from .filters import HashFilter as _HashFilter +from .filters import IntAsStringFilter as _IntAsStringFilter +from .filters import IntFilter as _IntFilter +from .filters import MessageTypeFilter as _MessageTypeFilter +from .filters import OrderBy as _OrderBy +from .filters import TokensFilter as _TokensFilter class Id(_HashFilter, _OrderBy): diff --git a/python/nekoton/gql/tx.py b/python/nekoton/gql/tx.py index bd5e17a..ddb8608 100644 --- a/python/nekoton/gql/tx.py +++ b/python/nekoton/gql/tx.py @@ -1,6 +1,11 @@ -from .filters import IntFilter as _IntFilter, OrderBy as _OrderBy, HashFilter as _HashFilter, BoolFilter as _BoolFilter, \ - AddressFilter as _AddressFilter, TokensFilter as _TokensFilter, IntAsStringFilter as _IntAsStringFilter, \ - TransactionTypeFilter as _TransactionTypeFilter +from .filters import AddressFilter as _AddressFilter +from .filters import BoolFilter as _BoolFilter +from .filters import HashFilter as _HashFilter +from .filters import IntAsStringFilter as _IntAsStringFilter +from .filters import IntFilter as _IntFilter +from .filters import OrderBy as _OrderBy +from .filters import TokensFilter as _TokensFilter +from .filters import TransactionTypeFilter as _TransactionTypeFilter class WorkchainId(_IntFilter, _OrderBy): diff --git a/python/nekoton/nekoton.pyi b/python/nekoton/nekoton.pyi index 9ef78ab..48e2b0f 100644 --- a/python/nekoton/nekoton.pyi +++ b/python/nekoton/nekoton.pyi @@ -1,13 +1,11 @@ from os import PathLike -from typing import Any, ClassVar, Optional, List, Tuple, Dict - +from typing import Any, ClassVar, Dict, List, Optional, Tuple ######### ## ABI ## ######### # - class TransactionExecutor: """ Local transaction executor. @@ -20,13 +18,14 @@ class TransactionExecutor: check_signature: bool """Whether to require valid signatures.""" - def __init__(self, config: BlockchainConfig, clock: Optional[Clock] = None, - check_signature: Optional[bool] = None) -> None: ... - + def __init__( + self, + config: BlockchainConfig, + clock: Optional[Clock] = None, + check_signature: Optional[bool] = None, + ) -> None: ... def execute( - self, - message: Message, - account: Optional[AccountState] = None + self, message: Message, account: Optional[AccountState] = None ) -> Tuple[Transaction, Optional[AccountState]]: """ Executes the specified message on account state. @@ -36,7 +35,6 @@ class TransactionExecutor: """ ... - class ContractAbi: """ Parsed contract ABI. @@ -45,12 +43,11 @@ class ContractAbi: """ @staticmethod - def from_file(file: str | bytes | PathLike[str] | PathLike[bytes]) -> ContractAbi: + def from_file(path: str | bytes | PathLike[str] | PathLike[bytes]) -> ContractAbi: """Reads ABI from file.""" ... def __init__(self, abi: str) -> None: ... - @property def abi_version(self) -> AbiVersion: """TVM ABI version.""" @@ -64,6 +61,14 @@ class ContractAbi: """ ... + def function(self, name: str) -> FunctionAbi: + """ + Searches for the function ABI with the specified name. + + :param name: function name. + """ + ... + def get_event(self, name: str) -> Optional[EventAbi]: """ Searches for the event ABI with the specified name. @@ -72,11 +77,35 @@ class ContractAbi: """ ... + def event(self, name: str) -> EventAbi: + """ + Searches for the event ABI with the specified name. + + :param name: event name. + """ + ... + + def get_getter(self, name: str) -> Optional[GetterAbi]: + """ + Searches for the getter ABI with the specified name. + + :param name: getter name. + """ + ... + + def getter(self, name: str) -> GetterAbi: + """ + Searches for the getter ABI with the specified name. + + :param name: getter name. + """ + ... + def encode_init_data( - self, - data: Dict[str, Any], - public_key: Optional[PublicKey] = None, - existing_data: Optional[Cell] = None + self, + data: Dict[str, Any], + public_key: Optional[PublicKey] = None, + existing_data: Optional[Cell] = None, ) -> Cell: """ Encodes initial contract data using the specified values and public key. @@ -87,7 +116,9 @@ class ContractAbi: """ ... - def decode_init_data(self, data: Cell) -> Tuple[Optional[PublicKey], Dict[str, Any]]: + def decode_init_data( + self, data: Cell + ) -> Tuple[Optional[PublicKey], Dict[str, Any]]: """ Decodes initial contract data using the contract ABI. @@ -95,7 +126,9 @@ class ContractAbi: """ ... - def decode_fields(self, data: Cell | AccountState) -> Dict[str, Any]: + def decode_fields( + self, data: Cell | AccountState, allow_partial: Optional[bool] = None + ) -> Dict[str, Any]: """ Decodes fields from the contract data. @@ -103,7 +136,9 @@ class ContractAbi: """ ... - def decode_transaction(self, transaction: Transaction) -> Optional[FunctionCallFull]: + def decode_transaction( + self, transaction: Transaction + ) -> Optional[FunctionCallFull]: """ Decodes function call and events from the specified transaction. @@ -111,7 +146,9 @@ class ContractAbi: """ ... - def decode_transaction_events(self, transaction: Transaction) -> List[Tuple[EventAbi, Dict[str, Any]]]: + def decode_transaction_events( + self, transaction: Transaction + ) -> List[Tuple[EventAbi, Dict[str, Any]]]: """ Decodes only events from the specified transaction. @@ -119,7 +156,6 @@ class ContractAbi: """ ... - class FunctionAbi: """Parsed function ABI.""" @@ -152,11 +188,12 @@ class FunctionAbi: ... def call( - self, - account_state: AccountState, - input: Dict, - responsible: Optional[bool] = None, - clock: Optional[Clock] = None, + self, + account_state: AccountState, + input: Dict, + responsible: Optional[bool] = None, + clock: Optional[Clock] = None, + config: Optional[BlockchainConfig] = None, ) -> ExecutionOutput: """ Runs this function as a getter. @@ -169,13 +206,13 @@ class FunctionAbi: ... def encode_external_message( - self, - dst: Address, - input: Dict[str, Any], - public_key: Optional[PublicKey], - state_init: Optional[StateInit] = None, - timeout: Optional[int] = None, - clock: Optional[Clock] = None + self, + dst: Address, + input: Dict[str, Any], + public_key: Optional[PublicKey] = None, + state_init: Optional[StateInit] = None, + timeout: Optional[int] = None, + clock: Optional[Clock] = None, ) -> UnsignedExternalMessage: """ Encodes external message using the function ABI. @@ -190,12 +227,12 @@ class FunctionAbi: ... def encode_external_input( - self, - input: Dict[str, Any], - public_key: Optional[PublicKey], - timeout: Optional[int] = None, - address: Optional[Address] = None, - clock: Optional[Clock] = None + self, + input: Dict[str, Any], + public_key: Optional[PublicKey] = None, + timeout: Optional[int] = None, + address: Optional[Address] = None, + clock: Optional[Clock] = None, ) -> UnsignedBody: """ Encodes external function input using the function ABI. @@ -209,13 +246,13 @@ class FunctionAbi: ... def encode_internal_message( - self, - input: Dict[str, Any], - value: Tokens, - bounce: bool, - dst: Address, - src: Optional[Address] = None, - state_init: Optional[StateInit] = None, + self, + input: Dict[str, Any], + value: Tokens, + bounce: bool, + dst: Address, + src: Optional[Address] = None, + state_init: Optional[StateInit] = None, ) -> Message: """ Encodes internal message using the function ABI. @@ -246,10 +283,7 @@ class FunctionAbi: ... def decode_input( - self, - message_body: Cell, - internal: bool, - allow_partial: Optional[bool] = None + self, message_body: Cell, internal: bool, allow_partial: Optional[bool] = None ) -> Dict[str, Any]: """ Decodes message body as input using the function ABI. @@ -260,7 +294,9 @@ class FunctionAbi: """ ... - def decode_output(self, message_body: Cell, allow_partial: Optional[bool] = None) -> Dict[str, Any]: + def decode_output( + self, message_body: Cell, allow_partial: Optional[bool] = None + ) -> Dict[str, Any]: """ Decodes message body as output using the function ABI. @@ -270,38 +306,32 @@ class FunctionAbi: ... def __eq__(self, other) -> Any: ... - def __ge__(self, other) -> Any: ... - def __gt__(self, other) -> Any: ... - def __hash__(self) -> Any: ... - def __le__(self, other) -> Any: ... - def __lt__(self, other) -> Any: ... - def __ne__(self, other) -> Any: ... - class FunctionAbiWithArgs: """Parsed function ABI.""" @property - def abi(self) -> AbiVersion: + def abi(self) -> FunctionAbi: """Returns an underlying function ABI.""" ... @property - def args(self) -> str: + def args(self) -> Dict[str, Any]: """Returns function input args.""" ... def call( - self, - account_state: AccountState, - responsible: Optional[bool] = None, - clock: Optional[Clock] = None, + self, + account_state: AccountState, + responsible: Optional[bool] = None, + clock: Optional[Clock] = None, + config: Optional[BlockchainConfig] = None, ) -> ExecutionOutput: """ Runs this function as a getter. @@ -313,12 +343,12 @@ class FunctionAbiWithArgs: ... def encode_external_message( - self, - dst: Address, - public_key: Optional[PublicKey], - state_init: Optional[StateInit] = None, - timeout: Optional[int] = None, - clock: Optional[Clock] = None + self, + dst: Address, + public_key: Optional[PublicKey] = None, + state_init: Optional[StateInit] = None, + timeout: Optional[int] = None, + clock: Optional[Clock] = None, ) -> UnsignedExternalMessage: """ Encodes external message using the function ABI. @@ -332,11 +362,11 @@ class FunctionAbiWithArgs: ... def encode_external_input( - self, - public_key: Optional[PublicKey], - timeout: Optional[int] = None, - address: Optional[Address] = None, - clock: Optional[Clock] = None + self, + public_key: Optional[PublicKey] = None, + timeout: Optional[int] = None, + address: Optional[Address] = None, + clock: Optional[Clock] = None, ) -> UnsignedBody: """ Encodes external function input using the function ABI. @@ -349,12 +379,12 @@ class FunctionAbiWithArgs: ... def encode_internal_message( - self, - value: Tokens, - bounce: bool, - dst: Address, - src: Optional[Address] = None, - state_init: Optional[StateInit] = None, + self, + value: Tokens, + bounce: bool, + dst: Address, + src: Optional[Address] = None, + state_init: Optional[StateInit] = None, ) -> Message: """ Encodes internal message using the function ABI. @@ -374,20 +404,13 @@ class FunctionAbiWithArgs: ... def __eq__(self, other) -> Any: ... - def __ge__(self, other) -> Any: ... - def __gt__(self, other) -> Any: ... - def __hash__(self) -> Any: ... - def __le__(self, other) -> Any: ... - def __lt__(self, other) -> Any: ... - def __ne__(self, other) -> Any: ... - class ExecutionOutput: @property def exit_code(self) -> int: @@ -399,7 +422,6 @@ class ExecutionOutput: """Parsed output in case of successful execution.""" ... - class FunctionCall: """Parsed function call.""" @@ -413,7 +435,6 @@ class FunctionCall: """Parsed function output.""" ... - class FunctionCallFull(FunctionCall): """Extended parsed function cell.""" @@ -427,7 +448,6 @@ class FunctionCallFull(FunctionCall): """ABI object of the parsed function""" ... - class EventAbi: """Parsed event ABI.""" @@ -463,19 +483,49 @@ class EventAbi: ... def __eq__(self, other) -> Any: ... - def __ge__(self, other) -> Any: ... - def __gt__(self, other) -> Any: ... - def __hash__(self) -> Any: ... - def __le__(self, other) -> Any: ... - def __lt__(self, other) -> Any: ... - def __ne__(self, other) -> Any: ... +class GetterAbi: + """Parsed getter ABI.""" + + @property + def name(self) -> str: + """Event name.""" + ... + + @property + def method_id(self) -> int: + """Method id.""" + ... + + def call( + self, + account_state: AccountState, + input: Dict, + clock: Optional[Clock] = None, + config: Optional[BlockchainConfig] = None, + ) -> ExecutionOutput: + """ + Runs this function as a getter. + + :param account_state: a state of existing account which will be used for execution. + :param input: function intput. + :param clock: optional clock to modify execution timestamp. + """ + ... + + def __eq__(self, other) -> Any: ... + def __ge__(self, other) -> Any: ... + def __gt__(self, other) -> Any: ... + def __hash__(self) -> Any: ... + def __le__(self, other) -> Any: ... + def __lt__(self, other) -> Any: ... + def __ne__(self, other) -> Any: ... class Message: """ @@ -487,9 +537,7 @@ class Message: header: MessageHeader, body: Optional[Cell] = None, state_init: Optional[StateInit] = None, - ) -> None: - ... - + ) -> None: ... @staticmethod def from_bytes(bytes: bytes) -> Message: """ @@ -605,34 +653,25 @@ class Message: ... def __eq__(self, other) -> Any: ... - def __ge__(self, other) -> Any: ... - def __gt__(self, other) -> Any: ... - def __hash__(self) -> Any: ... - def __le__(self, other) -> Any: ... - def __lt__(self, other) -> Any: ... - def __ne__(self, other) -> Any: ... - class SignedExternalMessage(Message): """ External message with an additional expiration param. """ def __init__( - self, - dst: Address, - expire_at: int, - body: Optional[Cell] = None, - state_init: Optional[StateInit] = None - ) -> None: - ... - + self, + dst: Address, + expire_at: int, + body: Optional[Cell] = None, + state_init: Optional[StateInit] = None, + ) -> None: ... @property def expire_at(self) -> int: """Expiration unix timestamp.""" @@ -642,7 +681,6 @@ class SignedExternalMessage(Message): """Splits into inner message and expiration timestamp.""" ... - class UnsignedExternalMessage: """Unsigned external message with function intput.""" @@ -659,12 +697,14 @@ class UnsignedExternalMessage: """Expiration unix timestamp.""" ... - def sign(self, keypair: KeyPair, signature_id: Optional[int]) -> SignedExternalMessage: + def sign( + self, keypair: KeyPair, context: Optional[int] | SignatureContext + ) -> SignedExternalMessage: """ Signs function input with the specified keypair and signature id. :param keypair: signer keypair. - :param signature_id: optional signature id. + :param context: optional signature id or signature context. """ ... @@ -684,7 +724,6 @@ class UnsignedExternalMessage: """Creates an input without a signature.""" ... - class UnsignedBody: """Unsigned function input.""" @@ -698,12 +737,12 @@ class UnsignedBody: """Expiration unix timestamp.""" ... - def sign(self, keypair: KeyPair, signature_id: Optional[int]) -> Cell: + def sign(self, keypair: KeyPair, context: Optional[int] | SignatureContext) -> Cell: """ Signs function input with the specified keypair and signature id. :param keypair: signer keypair. - :param signature_id: optional signature id. + :param context: optional signature id or signature context. """ ... @@ -723,13 +762,11 @@ class UnsignedBody: """Creates an input without a signature.""" ... - class AbiParam: """ Base ABI type. """ - class AbiUint(AbiParam): """ A class for `uintN` ABI type. @@ -739,7 +776,6 @@ class AbiUint(AbiParam): def __init__(self, size: int) -> None: ... - class AbiInt(AbiParam): """ A class for an `intN` ABI type. @@ -749,7 +785,6 @@ class AbiInt(AbiParam): def __init__(self, size: int) -> None: ... - class AbiVarUint(AbiParam): """ A class for `varuintN` ABI type. @@ -759,7 +794,6 @@ class AbiVarUint(AbiParam): def __init__(self, size: int) -> None: ... - class AbiVarInt(AbiParam): """ A class for `varintN` ABI type. @@ -769,7 +803,6 @@ class AbiVarInt(AbiParam): def __init__(self, size: int) -> None: ... - class AbiBool(AbiParam): """ A class for a `bool` ABI type. @@ -777,7 +810,6 @@ class AbiBool(AbiParam): def __init__(self) -> None: ... - class AbiTuple(AbiParam): """ A class for `(T1, T2, ..., Tn)` ABI type. @@ -787,7 +819,6 @@ class AbiTuple(AbiParam): def __init__(self, items: List[Tuple[str, AbiParam]]) -> None: ... - class AbiArray(AbiParam): """ A class for an `T[]` ABI type. @@ -797,7 +828,6 @@ class AbiArray(AbiParam): def __init__(self, value_type: AbiParam) -> None: ... - class AbiFixedArray(AbiParam): """ A class for a `T[N]` ABI type. @@ -808,7 +838,6 @@ class AbiFixedArray(AbiParam): def __init__(self, value_type: AbiParam, len: int) -> None: ... - class AbiCell(AbiParam): """ A class for a `cell` ABI type. @@ -816,7 +845,6 @@ class AbiCell(AbiParam): def __init__(self) -> None: ... - class AbiMap(AbiParam): """ A class for an `map(K, V)` ABI type. @@ -827,7 +855,6 @@ class AbiMap(AbiParam): def __init__(self, key_type: AbiParam, value_type: AbiParam) -> None: ... - class AbiAddress(AbiParam): """ A class for a `address` ABI type. @@ -835,6 +862,12 @@ class AbiAddress(AbiParam): def __init__(self) -> None: ... +class AbiAddressStd(AbiParam): + """ + A class for a `address_std` ABI type. + """ + + def __init__(self) -> None: ... class AbiBytes(AbiParam): """ @@ -843,7 +876,6 @@ class AbiBytes(AbiParam): def __init__(self) -> None: ... - class AbiFixedBytes(AbiParam): """ A class for a `fixedbytesN` ABI type. @@ -853,7 +885,6 @@ class AbiFixedBytes(AbiParam): def __init__(self, len: int) -> None: ... - class AbiString(AbiParam): """ A class for `string` ABI type. @@ -861,7 +892,6 @@ class AbiString(AbiParam): def __init__(self) -> None: ... - class AbiToken(AbiParam): """ A class for `token` ABI type. @@ -869,7 +899,6 @@ class AbiToken(AbiParam): def __init__(self) -> None: ... - class AbiOptional(AbiParam): """ A class for an `optional(T)` ABI type. @@ -879,7 +908,6 @@ class AbiOptional(AbiParam): def __init__(self, value_type: AbiParam) -> None: ... - class AbiRef(AbiParam): """ A class for a `ref(T)` ABI type. @@ -889,7 +917,6 @@ class AbiRef(AbiParam): def __init__(self, value_type: AbiParam) -> None: ... - class AbiVersion: """ TVM ABI version. @@ -905,22 +932,15 @@ class AbiVersion: """Minor TVM ABI version component.""" def __init__(self, major: int, minor: int) -> None: ... - + def __str__(self) -> str: ... def __eq__(self, other) -> Any: ... - def __ge__(self, other) -> Any: ... - def __gt__(self, other) -> Any: ... - def __hash__(self) -> Any: ... - def __le__(self, other) -> Any: ... - def __lt__(self, other) -> Any: ... - def __ne__(self, other) -> Any: ... - # ############ @@ -928,17 +948,31 @@ class AbiVersion: ############ # - class BlockchainConfig: """ Partially parsed blockchain config. """ + @property + def global_id(self) -> int: + """Network ID.""" + ... + @property def capabilities(self) -> int: """Required software capabilities as integer mask.""" ... + @property + def signature_id(self) -> Optional[int]: + """Optional signature id when capability is enabled.""" + ... + + @property + def signature_context(self) -> SignatureContext: + """Network ID and capabilities that are used for signing.""" + ... + @property def global_version(self) -> int: """Required software version.""" @@ -988,7 +1022,6 @@ class BlockchainConfig: """ ... - class AccountState: """ A state of an existing account. @@ -1034,7 +1067,6 @@ class AccountState: """A hash of the last known state for the frozen account.""" ... - class StorageUsed: """ Account storage stats. @@ -1050,12 +1082,6 @@ class StorageUsed: """Number of bits occupied by this account.""" ... - @property - def public_cells(self) -> int: - """Number of public cells (libraries) provided by this account.""" - ... - - class Transaction: """Blockchain transaction.""" @@ -1222,20 +1248,13 @@ class Transaction: ... def __eq__(self, other) -> Any: ... - def __ge__(self, other) -> Any: ... - def __gt__(self, other) -> Any: ... - def __hash__(self) -> Any: ... - def __le__(self, other) -> Any: ... - def __lt__(self, other) -> Any: ... - def __ne__(self, other) -> Any: ... - class TransactionStoragePhase: """Transaction storage phase.""" @@ -1254,7 +1273,6 @@ class TransactionStoragePhase: """Account status change during this phase.""" ... - class TransactionCreditPhase: """Transaction credit phase.""" @@ -1268,101 +1286,72 @@ class TransactionCreditPhase: """Increased balance in nano EVERs.""" ... - class TransactionComputePhase: """Transaction compute phase.""" @property def success(self) -> bool: ... - @property def msg_state_used(self) -> bool: ... - @property def account_activated(self) -> bool: ... - @property def gas_fees(self) -> Tokens: ... - @property def gas_used(self) -> int: ... - @property def gas_limit(self) -> int: ... - @property def gas_credit(self) -> Optional[int]: ... - @property def mode(self) -> int: ... - @property def exit_code(self) -> int: ... - @property def exit_arg(self) -> Optional[int]: ... - @property def vm_steps(self) -> int: ... - @property def vm_init_state_hash(self) -> bytes: ... - @property def vm_final_state_hash(self) -> bytes: ... - class TransactionActionPhase: """Transaction action phase.""" @property def success(self) -> bool: ... - @property def valid(self) -> bool: ... - @property def no_funds(self) -> bool: ... - @property def status_change(self) -> AccountStatusChange: ... - @property def total_fwd_fees(self) -> Optional[Tokens]: ... - @property def total_action_fees(self) -> Optional[Tokens]: ... - @property def result_code(self) -> int: ... - @property def result_arg(self) -> Optional[int]: ... - @property def total_actions(self) -> int: ... - @property def special_actions(self) -> int: ... - @property def skipped_actions(self) -> int: ... - @property def messages_created(self) -> int: ... - @property def action_list_hash(self) -> bytes: ... - class TransactionBouncePhase: @property def msg_fees(self) -> Tokens: ... - @property def fwd_fees(self) -> Tokens: ... - class TransactionType: Ordinary: ClassVar[TransactionType] = ... """Ordinary transaction.""" @@ -1380,23 +1369,16 @@ class TransactionType: """ ... + def __str__(self) -> str: ... def __eq__(self, other) -> Any: ... - def __ge__(self, other) -> Any: ... - def __gt__(self, other) -> Any: ... - def __hash__(self) -> Any: ... - def __int__(self) -> Any: ... - def __le__(self, other) -> Any: ... - def __lt__(self, other) -> Any: ... - def __ne__(self, other) -> Any: ... - class TransactionTree: @staticmethod def from_bytes(bytes: bytes) -> TransactionTree: @@ -1425,7 +1407,7 @@ class TransactionTree: ... @property - def children(self) -> List[Transaction]: + def children(self) -> List[TransactionTree]: """ Get list of children nodes """ @@ -1433,17 +1415,14 @@ class TransactionTree: def __iter__(self) -> TransactionTreeIter: ... - class TransactionTreeIter: """ Plain transaction tree iterator. """ def __iter__(self) -> TransactionTreeIter: ... - def __next__(self) -> Transaction: ... - class AccountStatus: """ Account status. @@ -1461,23 +1440,16 @@ class AccountStatus: Uninit: ClassVar[AccountStatus] = ... """Account without a state.""" + def __str__(self) -> str: ... def __eq__(self, other) -> Any: ... - def __ge__(self, other) -> Any: ... - def __gt__(self, other) -> Any: ... - def __hash__(self) -> Any: ... - def __int__(self) -> Any: ... - def __le__(self, other) -> Any: ... - def __lt__(self, other) -> Any: ... - def __ne__(self, other) -> Any: ... - class AccountStatusChange: """ Account status change during transaction phase. @@ -1492,31 +1464,24 @@ class AccountStatusChange: Unchanged: ClassVar[AccountStatusChange] = ... """Account status has not changed.""" + def __str__(self) -> str: ... def __eq__(self, other) -> Any: ... - def __ge__(self, other) -> Any: ... - def __gt__(self, other) -> Any: ... - def __hash__(self) -> Any: ... - def __int__(self) -> Any: ... - def __le__(self, other) -> Any: ... - def __lt__(self, other) -> Any: ... - def __ne__(self, other) -> Any: ... - class MessageHeader: """Base message header.""" + @property def type(self) -> MessageType: """Message type.""" ... - class InternalMessageHeader(MessageHeader): """Internal message header.""" @@ -1532,40 +1497,28 @@ class InternalMessageHeader(MessageHeader): fwd_fee: Optional[Tokens] = None, created_lt: Optional[int] = None, created_at: Optional[int] = None, - ) -> None: - ... - + ) -> None: ... @property def ihr_disabled(self) -> bool: ... - @property def bounce(self) -> bool: ... - @property def bounced(self) -> bool: ... - @property def src(self) -> Address: ... - @property def dst(self) -> Address: ... - @property def value(self) -> Tokens: ... - @property def ihr_fee(self) -> Tokens: ... - @property def fwd_fee(self) -> Tokens: ... - @property def created_at(self) -> int: ... - @property def created_lt(self) -> int: ... - class ExternalInMessageHeader(MessageHeader): """External incoming message header.""" @@ -1573,9 +1526,7 @@ class ExternalInMessageHeader(MessageHeader): self, dst: Address, import_fee: Optional[Tokens] = None, - ) -> None: - ... - + ) -> None: ... @property def dst(self) -> Address: """Message destination.""" @@ -1586,18 +1537,15 @@ class ExternalInMessageHeader(MessageHeader): """Import fee in nano EVERs""" ... - class ExternalOutMessageHeader(MessageHeader): """External outgoing message header.""" def __init__( self, - src: Optional[Address], + src: Optional[Address] = None, created_lt: Optional[int] = None, created_at: Optional[int] = None, - ) -> None: - ... - + ) -> None: ... @property def src(self) -> Address: """Message source.""" @@ -1613,7 +1561,6 @@ class ExternalOutMessageHeader(MessageHeader): """A logical time when the message was created.""" ... - class MessageType: """Message type.""" @@ -1626,23 +1573,16 @@ class MessageType: ExternalOut: ClassVar[MessageType] = ... """External outgoing message. (Events).""" + def __str__(self) -> str: ... def __eq__(self, other) -> Any: ... - def __ge__(self, other) -> Any: ... - def __gt__(self, other) -> Any: ... - def __hash__(self) -> Any: ... - def __int__(self) -> Any: ... - def __le__(self, other) -> Any: ... - def __lt__(self, other) -> Any: ... - def __ne__(self, other) -> Any: ... - class StateInit: """ Contract code and data. @@ -1685,14 +1625,15 @@ class StateInit: """ ... - def __init__(self, code: Optional[Cell], data: Optional[Cell]) -> None: ... - + def __init__( + self, code: Optional[Cell] = None, data: Optional[Cell] = None + ) -> None: ... @property def code_hash(self) -> Optional[bytes]: """Optional code hash.""" ... - def set_code_salt(self, salt: Cell): + def set_code_salt(self, salt: Cell) -> None: """ Tries to update the code salt. @@ -1730,7 +1671,6 @@ class StateInit: """Creates a new cell with StateInit.""" ... - class Address: """ Account address (`StdAddr`). @@ -1751,7 +1691,7 @@ class Address: ... @staticmethod - def from_parts(workchain: int, account: bytes) -> Any: + def from_parts(workchain: int, account: bytes) -> "Address": """ Creates an address from parts. @@ -1761,39 +1701,49 @@ class Address: ... def __init__(self, addr: str) -> None: ... - @property def account(self) -> bytes: """Hash of the initial state.""" ... - def to_base64(self, url_safe: bool = True, bounce: bool = False): + def to_base64(self, url_safe: bool = True, bounce: bool = False) -> str: """ Encodes address to a base64 format. """ ... - def __str__(self): + def as_cell(self) -> Cell: + """ + Builds a cell with stored address. + """ + ... + + def as_slice(self) -> CellSlice: + """ + Builds a cell slice with stored address. + """ + ... + + def as_builder(self) -> CellBuilder: + """ + Creates a new cell builder and stores this address in it. + """ + ... + + def __str__(self) -> str: """ Encodes address to a raw format. """ ... def __eq__(self, other) -> Any: ... - def __ge__(self, other) -> Any: ... - def __gt__(self, other) -> Any: ... - def __hash__(self) -> Any: ... - def __le__(self, other) -> Any: ... - def __lt__(self, other) -> Any: ... - def __ne__(self, other) -> Any: ... - class CellSlice: """ A read-only view for a subrange of a cell. @@ -1824,6 +1774,18 @@ class CellSlice: """Returns the start of the references window.""" ... + def advance(self, bits=None, refs=None) -> None: + """ + Skips a slice prefix. + """ + ... + + def shrink(self, bits=None, refs=None) -> None: + """ + Removes all but the specified prefix. + """ + ... + def is_empty(self) -> bool: """Returns whether there are no bits and refs left.""" ... @@ -1968,17 +1930,15 @@ class CellSlice: """Tries to read the next cell, incrementing the refs window start.""" ... - class CellBuilder: """ Builder for constructing cells with densely packed data. """ - is_exotic: Optional[Cell] + is_exotic: bool """Whether this cell will be built as an exotic.""" def __init__(self) -> None: ... - @property def bits(self) -> int: """Data length in bits.""" @@ -2003,111 +1963,111 @@ class CellBuilder: """Tries to build a new cell from the builder.""" ... - def store_zeros(self, bits: int): + def store_zeros(self, bits: int) -> None: """Tries to store the specified number of zero bits into the cell.""" ... - def store_ones(self, bits: int): + def store_ones(self, bits: int) -> None: """Tries to store the specified number of set bits into the cell.""" ... - def store_bit_zero(self): + def store_bit_zero(self) -> None: """Tries to store one zero bit into the cell.""" ... - def store_bit_one(self): + def store_bit_one(self) -> None: """Tries to store one non-zero bit into the cell.""" ... - def store_bit(self, value: bool): + def store_bit(self, value: bool) -> None: """Tries to store one bit into the cell.""" ... - def store_u8(self, value: int): + def store_u8(self, value: int) -> None: """Tries to store u8 into the cell.""" ... - def store_i8(self, value: int): + def store_i8(self, value: int) -> None: """Tries to store i8 into the cell.""" ... - def store_u16(self, value: int): + def store_u16(self, value: int) -> None: """Tries to store u16 into the cell.""" ... - def store_i16(self, value: int): + def store_i16(self, value: int) -> None: """Tries to store i16 into the cell.""" ... - def store_u32(self, value: int): + def store_u32(self, value: int) -> None: """Tries to store u32 into the cell.""" ... - def store_i32(self, value: int): + def store_i32(self, value: int) -> None: """Tries to store i32 into the cell.""" ... - def store_u64(self, value: int): + def store_u64(self, value: int) -> None: """Tries to store u64 into the cell.""" ... - def store_i64(self, value: int): + def store_i64(self, value: int) -> None: """Tries to store i64 into the cell.""" ... - def store_u128(self, value: int): + def store_u128(self, value: int) -> None: """Tries to store u128 into the cell.""" ... - def store_i128(self, value: int): + def store_i128(self, value: int) -> None: """Tries to store i128 into the cell.""" ... - def store_uint(self, value: int, bits: int): + def store_uint(self, value: int, bits: int) -> None: """Tries to store an unsigned integer into the cell.""" ... - def store_int(self, value: int, bits: int): + def store_int(self, value: int, bits: int) -> None: """Tries to store a signed integer into the cell.""" ... - def store_public_key(self, public_key: PublicKey): + def store_public_key(self, value: PublicKey) -> None: """Tries to store a public key into the cell.""" ... - def store_signature(self, signature: Signature): + def store_signature(self, signature: Signature) -> None: """Tries to store a signature into the cell.""" ... - def store_bytes(self, bytes: bytes): + def store_bytes(self, bytes: bytes) -> None: """Tries to store bytes into the cell.""" ... - def store_raw(self, bytes: bytes, bits: int): + def store_raw(self, bytes: bytes, bits: int) -> None: """Tries to store a raw data into the cell.""" ... - def store_reference(self, cell: Cell): + def store_reference(self, cell: Cell) -> None: """Tries to store a child into the cell.""" ... - def store_builder(self, builder: CellBuilder): + def store_builder(self, value: CellBuilder) -> None: """Tries to append a builder.""" ... - def store_slice(self, slice: CellSlice): + def store_slice(self, value: CellSlice) -> None: """Tries to append a cell slice.""" ... def store_abi( + self, abi: List[Tuple[str, AbiParam]], value: Dict[str, Any], abi_version: Optional[AbiVersion] = None, - ): + ) -> None: """Tries to store an abi encoded value into the cell.""" ... - class Cell: """ A container with up to 1023 bits of data and up to 4 children. @@ -2124,9 +2084,9 @@ class Cell: @staticmethod def build( - abi: List[Tuple[str, AbiParam]], - value: Dict[str, Any], - abi_version: Optional[AbiVersion] = None, + abi: List[Tuple[str, AbiParam]], + value: Dict[str, Any], + abi_version: Optional[AbiVersion] = None, ) -> Cell: """ Packs values into cell using the provided ABI. @@ -2148,7 +2108,6 @@ class Cell: ... def __init__(self) -> None: ... - @property def repr_hash(self) -> bytes: """Representation hash of the cell.""" @@ -2181,10 +2140,10 @@ class Cell: ... def unpack( - self, - abi: List[Tuple[str, AbiParam]], - abi_version: Optional[AbiVersion] = None, - allow_partial: Optional[bool] = None, + self, + abi: List[Tuple[str, AbiParam]], + abi_version: Optional[AbiVersion] = None, + allow_partial: Optional[bool] = None, ) -> Dict[str, Any]: """ Unpack values using the provided ABI. @@ -2209,20 +2168,13 @@ class Cell: ... def __eq__(self, other) -> Any: ... - def __ge__(self, other) -> Any: ... - def __gt__(self, other) -> Any: ... - def __hash__(self) -> Any: ... - def __le__(self, other) -> Any: ... - def __lt__(self, other) -> Any: ... - def __ne__(self, other) -> Any: ... - class Tokens: """ Wrapper around native currency. @@ -2233,7 +2185,7 @@ class Tokens: """Wraps amount in nano.""" ... - def __init__(self, value: str | int): + def __init__(self, value: str | int) -> None: """Constructs tokens from decimal or integer value.""" ... @@ -2260,42 +2212,25 @@ class Tokens: ... def abs(self) -> Tokens: ... - + def __str__(self) -> str: ... def __bool__(self) -> bool: ... - def __int__(self) -> int: ... - def __add__(self, other: Tokens) -> Tokens: ... - def __sub__(self, other: Tokens) -> Tokens: ... - def __mul__(self, other: int) -> Tokens: ... - def __rmul__(self, other: int) -> Tokens: ... - def __truediv__(self, other: int) -> Tokens: ... - def __pos__(self) -> Tokens: ... - def __neg__(self) -> Tokens: ... - def __abs__(self) -> Tokens: ... - def __eq__(self, other: Tokens) -> Any: ... - def __ge__(self, other: Tokens) -> Any: ... - def __gt__(self, other: Tokens) -> Any: ... - def __hash__(self) -> Any: ... - def __le__(self, other: Tokens) -> Any: ... - def __lt__(self, other: Tokens) -> Any: ... - def __ne__(self, other: Tokens) -> Any: ... - # ############### @@ -2303,7 +2238,6 @@ class Tokens: ############### # - class Transport: """Base transport""" @@ -2312,11 +2246,13 @@ class Transport: """Time context.""" ... - async def check_connection(self): + async def check_connection(self) -> None: """Checks the connection.""" ... - async def send_external_message(self, message: SignedExternalMessage) -> Optional[Transaction]: + async def send_external_message( + self, message: SignedExternalMessage + ) -> Optional[Transaction]: """ Sends an external message to the network and waits until the transaction. @@ -2328,7 +2264,13 @@ class Transport: """Fetches signature id for the selected network.""" ... - async def get_blockchain_config(self, force: Optional[bool] = None) -> BlockchainConfig: + async def get_signature_context(self) -> SignatureContext: + """Fetches signature context from the selected network.""" + ... + + async def get_blockchain_config( + self, force: Optional[bool] = None + ) -> BlockchainConfig: """ Fetches the latest blockchain config. @@ -2345,10 +2287,10 @@ class Transport: ... async def get_accounts_by_code_hash( - self, - code_hash: bytes, - continuation: Optional[Address] = None, - limit: Optional[int] = None, + self, + code_hash: bytes, + continuation: Optional[Address] = None, + limit: Optional[int] = None, ) -> List[Address]: """ Fetches a list of address of accounts with the specified code hash. @@ -2367,7 +2309,9 @@ class Transport: """ ... - async def get_dst_transaction(self, message_hash: bytes | Message) -> Optional[Transaction]: + async def get_dst_transaction( + self, message_hash: bytes | Message + ) -> Optional[Transaction]: """ Searches for a transaction by the hash of incoming message. @@ -2376,10 +2320,10 @@ class Transport: ... async def get_transactions( - self, - address: Address, - lt: Optional[int] = None, - limit: Optional[int] = None, + self, + address: Address, + lt: Optional[int] = None, + limit: Optional[int] = None, ) -> List[Transaction]: """ Fetches a transactions batch for the specified account. @@ -2406,7 +2350,9 @@ class Transport: """ ... - def trace_transaction(self, transaction_hash: bytes | Transaction, yield_root: bool = False) -> TraceTransaction: + def trace_transaction( + self, transaction_hash: bytes | Transaction, yield_root: bool = False + ) -> TraceTransaction: """ Returns an async transactions iterator over the transactions tree. @@ -2415,7 +2361,6 @@ class Transport: """ ... - class GqlTransport(Transport): """ GraphQl transport. @@ -2426,17 +2371,16 @@ class GqlTransport(Transport): """ def __init__( - self, - endpoints: List[str], - clock: Optional[Clock] = None, - local: Optional[bool] = None, + self, + endpoints: List[str], + clock: Optional[Clock] = None, + local: Optional[bool] = None, ) -> None: ... - async def query_transactions( - self, - filter: str | GqlExprPart | List[GqlExprPart], - order_by: Optional[str | GqlExprPart | List[GqlExprPart]] = None, - limit: Optional[int] = None + self, + filter: str | GqlExprPart | List[GqlExprPart], + order_by: Optional[str | GqlExprPart | List[GqlExprPart]] = None, + limit: Optional[int] = None, ) -> List[Transaction]: """ Transactions GQL query. @@ -2448,11 +2392,11 @@ class GqlTransport(Transport): ... async def query_messages( - self, - filter: str | GqlExprPart | List[GqlExprPart], - order_by: Optional[str | GqlExprPart | List[GqlExprPart]] = None, - limit: Optional[int] = None - ) -> List[Transaction]: + self, + filter: str | GqlExprPart | List[GqlExprPart], + order_by: Optional[str | GqlExprPart | List[GqlExprPart]] = None, + limit: Optional[int] = None, + ) -> List[Message]: """ Messages GQL query. @@ -2463,10 +2407,10 @@ class GqlTransport(Transport): ... async def query_accounts( - self, - filter: str | GqlExprPart | List[GqlExprPart], - order_by: Optional[str | GqlExprPart | List[GqlExprPart]] = None, - limit: Optional[int] = None + self, + filter: str | GqlExprPart | List[GqlExprPart], + order_by: Optional[str | GqlExprPart | List[GqlExprPart]] = None, + limit: Optional[int] = None, ) -> List[Tuple[Address, Optional[AccountState]]]: """ Accounts GQL query. @@ -2477,7 +2421,6 @@ class GqlTransport(Transport): """ ... - class GqlExprPart: """ GQL query part. @@ -2485,10 +2428,8 @@ class GqlExprPart: :param value: part value. """ - def __init__(self, value: str): ... - - def __str__(self): ... - + def __init__(self, value: str) -> None: ... + def __str__(self) -> str: ... class JrpcTransport(Transport): """ @@ -2498,9 +2439,7 @@ class JrpcTransport(Transport): :param clock: optional clock to modify timestamp. """ - def __init__(self, endpoint: str, - clock: Optional[Clock] = None) -> None: ... - + def __init__(self, endpoint: str, clock: Optional[Clock] = None) -> None: ... class ProtoTransport(Transport): """ @@ -2510,75 +2449,61 @@ class ProtoTransport(Transport): :param clock: optional clock to modify timestamp. """ - def __init__(self, endpoint: str, - clock: Optional[Clock] = None) -> None: ... - + def __init__(self, endpoint: str, clock: Optional[Clock] = None) -> None: ... class AccountStatesAsyncIter: """ Async account states iterator. """ - async def close(self): + async def close(self) -> None: """ Closes async iterator. """ ... async def __aenter__(self) -> AccountStatesAsyncIter: ... - - async def __aexit__(self, exc_type, exc_val, exc_tb): ... - + async def __aexit__(self, exc_type, exc_val, exc_tb) -> None: ... def __aiter__(self) -> AccountStatesAsyncIter: ... - - def __anext__(self) -> Optional[AccountState]: ... - + async def __anext__(self) -> Optional[AccountState]: ... class AccountTransactionsAsyncIter: """ Async account transactions iterator. """ - async def close(self): + async def close(self) -> None: """ Closes async iterator. """ ... async def __aenter__(self) -> AccountTransactionsAsyncIter: ... - - async def __aexit__(self, exc_type, exc_val, exc_tb): ... - + async def __aexit__(self, exc_type, exc_val, exc_tb) -> None: ... def __aiter__(self) -> AccountTransactionsAsyncIter: ... - - def __anext__(self) -> Tuple[List[Transaction], TransactionsBatchInfo]: ... - + async def __anext__(self) -> Tuple[List[Transaction], TransactionsBatchInfo]: ... class TraceTransaction: """ Async transactions tree iterator. """ - async def close(self): + async def close(self) -> None: """ Closes async iterator. """ ... - async def wait(self): + async def wait(self) -> None: """ Waits for the last transaction. """ ... async def __aenter__(self) -> TraceTransaction: ... - - async def __aexit__(self, exc_type, exc_val, exc_tb): ... - + async def __aexit__(self, exc_type, exc_val, exc_tb) -> None: ... def __aiter__(self) -> TraceTransaction: ... - - def __anext__(self) -> Transaction: ... - + async def __anext__(self) -> Transaction: ... class TransactionsBatchInfo: """ @@ -2595,7 +2520,6 @@ class TransactionsBatchInfo: """The highest logical time in batch.""" ... - class Clock: """ Time context. @@ -2607,7 +2531,6 @@ class Clock: """Clock offset in milliseconds.""" def __init__(self, offset: Optional[int] = None) -> None: ... - @property def now_sec(self) -> int: """Returns current timestamp in seconds.""" @@ -2618,7 +2541,6 @@ class Clock: """Returns current timestamp in milliseconds.""" ... - # ############ @@ -2626,6 +2548,32 @@ class Clock: ############ # +class SignatureContext: + """ + Signature Context. + + :param global_id: a network ID. + :param capabilities: raw network capabilities. + """ + + def __init__(self, global_id: int, capabilities: int) -> None: ... + @property + def global_id(self) -> int: + """Network ID.""" + ... + + @property + def capabilities(self) -> int: + """Raw network capabilities.""" + ... + + def apply(self, data: bytes) -> bytes: + """ + Applies signature context prefix to data. + + :param data: data to sign. + """ + ... class PublicKey: """ @@ -2654,14 +2602,18 @@ class PublicKey: ... def __init__(self, value: str, encoding: Optional[str] = None) -> None: ... - - def check_signature(self, data: bytes, signature: Signature, signature_id: Optional[int] = None) -> bool: + def check_signature( + self, + data: bytes, + signature: Signature, + context: Optional[int] | SignatureContext, + ) -> bool: """ Returns `True` if the signature is correct. :param data: signed message. :param signature: signature to check. - :param signature_id: optional signature id. + :param context: optional signature id or signature context. """ ... @@ -2681,21 +2633,15 @@ class PublicKey: """Converts public key into integer.""" ... + def __str__(self) -> str: ... def __eq__(self, other) -> Any: ... - def __ge__(self, other) -> Any: ... - def __gt__(self, other) -> Any: ... - def __hash__(self) -> Any: ... - def __le__(self, other) -> Any: ... - def __lt__(self, other) -> Any: ... - def __ne__(self, other) -> Any: ... - class KeyPair: """ Ed25519 key pair. @@ -2709,7 +2655,6 @@ class KeyPair: ... def __init__(self, secret: bytes) -> None: ... - @property def secret_key(self) -> bytes: """Corresponding secret key.""" @@ -2720,49 +2665,49 @@ class KeyPair: """Corresponding public key.""" ... - def sign(self, data: bytes, signature_id: Optional[int]) -> Signature: + def sign(self, data: bytes, context: Optional[int] | SignatureContext) -> Signature: """ Signs a hash of the specified data. :param data: data to sign. - :param signature_id: optional signature id. + :param context: optional signature id or context. """ ... - def sign_raw(self, data: bytes, signature_id: Optional[int]) -> Signature: + def sign_raw( + self, data: bytes, context: Optional[int] | SignatureContext + ) -> Signature: """ Signs data as is. :param data: data to sign. - :param signature_id: optional signature id. + :param context: optional signature id or context. """ ... - def check_signature(self, data: bytes, signature: Signature, signature_id: Optional[int] = None) -> bool: + def check_signature( + self, + data: bytes, + signature: Signature, + context: Optional[int] | SignatureContext, + ) -> bool: """ Returns `True` if the signature is correct. :param data: signed message. :param signature: signature to check. - :param signature_id: optional signature id. + :param context: optional signature id or signature context. """ ... def __eq__(self, other) -> Any: ... - def __ge__(self, other) -> Any: ... - def __gt__(self, other) -> Any: ... - def __hash__(self) -> Any: ... - def __le__(self, other) -> Any: ... - def __lt__(self, other) -> Any: ... - def __ne__(self, other) -> Any: ... - class Signature: """ Ed25519 signature. @@ -2781,7 +2726,6 @@ class Signature: ... def __init__(self, value: str, encoding: Optional[str] = None) -> None: ... - def encode(self, encoding: Optional[str] = None) -> str: """ Encodes signature into string. @@ -2795,20 +2739,13 @@ class Signature: ... def __eq__(self, other) -> Any: ... - def __ge__(self, other) -> Any: ... - def __gt__(self, other) -> Any: ... - def __hash__(self) -> Any: ... - def __le__(self, other) -> Any: ... - def __lt__(self, other) -> Any: ... - def __ne__(self, other) -> Any: ... - class Seed: """Base seed.""" @@ -2817,6 +2754,7 @@ class Seed: """Number of words in phrase.""" ... + def __str__(self) -> str: ... class LegacySeed(Seed): """ @@ -2831,12 +2769,10 @@ class LegacySeed(Seed): ... def __init__(self, phrase: str) -> None: ... - def derive(self) -> KeyPair: """Derives a key pair.""" ... - class Bip39Seed(Seed): """ BIP39 seed. @@ -2859,7 +2795,6 @@ class Bip39Seed(Seed): ... def __init__(self, phrase: str) -> None: ... - def derive(self, path: Optional[str] = None) -> KeyPair: """ Derives a key pair using some derivation path. @@ -2870,7 +2805,6 @@ class Bip39Seed(Seed): # - ######### ## ASM ## ######### diff --git a/src/abi.rs b/src/abi.rs index 83e4192..ac91be2 100644 --- a/src/abi.rs +++ b/src/abi.rs @@ -3,11 +3,13 @@ use std::path::PathBuf; use std::sync::atomic::AtomicU64; use std::sync::Arc; +use nt::abi::AsGetterMethodId; +use num_traits::Zero; use pyo3::exceptions::*; use pyo3::prelude::*; use pyo3::types::*; use rand::Rng; -use ton_block::{GetRepresentationHash, Serializable}; +use ton_block::{Deserializable, GetRepresentationHash, Serializable}; use crate::crypto::{KeyPair, PublicKey, Signature}; use crate::models::*; @@ -130,7 +132,8 @@ impl ContractAbi { #[new] fn new(abi: &str) -> PyResult { - let contract = ton_abi::Contract::load(abi.trim()).handle_value_error()?; + let contract = + ton_abi::Contract::load(std::io::Cursor::new(abi.trim())).handle_value_error()?; let functions = contract .functions @@ -144,10 +147,17 @@ impl ContractAbi { .map(|(name, abi)| (name.clone(), EventAbi(Arc::new(abi.clone())))) .collect(); + let getters = contract + .getters + .iter() + .map(|(name, abi)| (name.clone(), GetterAbi(Arc::new(abi.clone())))) + .collect(); + let shared = Arc::new(SharedContractAbi { contract, functions, events, + getters, }); Ok(Self(shared)) @@ -162,55 +172,48 @@ impl ContractAbi { self.0.functions.get(name).cloned() } + fn function(&self, name: &str) -> PyResult { + match self.0.functions.get(name).cloned() { + Some(function) => Ok(function), + None => Err(PyKeyError::new_err(format!("Function not found: {name}"))), + } + } + fn get_event(&self, name: &str) -> Option { self.0.events.get(name).cloned() } + fn event(&self, name: &str) -> PyResult { + match self.0.events.get(name).cloned() { + Some(event) => Ok(event), + None => Err(PyKeyError::new_err(format!("Event not found: {name}"))), + } + } + + fn get_getter(&self, name: &str) -> Option { + self.0.getters.get(name).cloned() + } + + fn getter(&self, name: &str) -> PyResult { + match self.0.getters.get(name).cloned() { + Some(getter) => Ok(getter), + None => Err(PyKeyError::new_err(format!("Getter not found: {name}"))), + } + } + fn encode_init_data( &self, data: &PyDict, public_key: Option<&PublicKey>, existing_data: Option, ) -> PyResult { - let mut map = ton_types::HashmapE::with_hashmap( - ton_abi::Contract::DATA_MAP_KEYLEN, - existing_data.and_then(|Cell(cell)| cell.reference(0).ok()), - ); - - if let Some(public_key) = public_key { - map.set_builder( - serialize_state_init_data_key(0), - ton_types::BuilderData::new() - .append_raw(public_key.0.as_bytes(), 256) - .unwrap(), - ) - .handle_runtime_error()?; - } - - if !self.0.contract.data.is_empty() { - for (param_name, param) in &self.0.contract.data { - let value = match data.get_item(param_name) { - Some(value) => parse_token(¶m.value.kind, value)?, - None => { - return Err(PyValueError::new_err(format!( - "Param '{param_name}' not found" - ))) - } - }; - - let builder = value - .pack_into_chain(&self.0.contract.abi_version) - .handle_runtime_error()?; - - map.set_builder(serialize_state_init_data_key(param.key), &builder) - .handle_runtime_error()?; - } + if self.0.contract.abi_version < ton_abi::contract::ABI_VERSION_2_4 { + encode_init_data_dict(&self.0.contract, data, public_key, existing_data) + } else if let Some(existing_data) = existing_data { + update_init_data_plain(&self.0.contract, data, public_key, existing_data) + } else { + encode_init_data_plain(&self.0.contract, data, public_key) } - - map.write_to_new_cell() - .and_then(ton_types::BuilderData::into_cell) - .handle_runtime_error() - .map(Cell) } fn decode_init_data<'a>( @@ -218,35 +221,11 @@ impl ContractAbi { py: Python<'a>, data: &Cell, ) -> PyResult<(Option, &'a PyDict)> { - let pubkey = { - let map = ton_types::HashmapE::with_hashmap( - ton_abi::Contract::DATA_MAP_KEYLEN, - data.0.reference(0).ok(), - ); - - let value = map - .get(serialize_state_init_data_key(0)) - .handle_value_error()?; - match value { - Some(mut value) => { - let pubkey = value.get_next_hash().handle_value_error()?; - if pubkey.is_zero() { - None - } else { - Some(PublicKey( - ed25519_dalek::PublicKey::from_bytes(pubkey.as_slice()) - .handle_value_error()?, - )) - } - } - None => None, - } - }; - - let data = ton_types::SliceData::load_cell_ref(&data.0).handle_value_error()?; - - let tokens = self.0.contract.decode_data(data).handle_value_error()?; - Ok((pubkey, convert_tokens(py, tokens)?)) + if self.0.contract.abi_version < ton_abi::contract::ABI_VERSION_2_4 { + decode_init_data_dict(py, &self.0.contract, data) + } else { + decode_init_data_plain(py, &self.0.contract, data) + } } fn decode_fields<'a>( @@ -314,7 +293,7 @@ impl ContractAbi { let input = function .0 - .decode_input(in_msg_body, in_msg.is_internal()) + .decode_input(in_msg_body, in_msg.is_internal(), false) .handle_runtime_error()?; let mut output = None; @@ -414,10 +393,222 @@ impl ContractAbi { } } +fn encode_init_data_dict( + contract: &ton_abi::Contract, + data: &PyDict, + public_key: Option<&PublicKey>, + existing_data: Option, +) -> PyResult { + let mut map = ton_types::HashmapE::with_hashmap( + ton_abi::Contract::DATA_MAP_KEYLEN, + existing_data.and_then(|Cell(cell)| cell.reference(0).ok()), + ); + + if let Some(public_key) = public_key { + map.set_builder( + serialize_state_init_data_key(0), + ton_types::BuilderData::new() + .append_raw(public_key.0.as_bytes(), 256) + .unwrap(), + ) + .handle_runtime_error()?; + } + + if !contract.data.is_empty() { + for (param_name, param) in &contract.data { + let value = match data.get_item(param_name) { + Some(value) => parse_token(¶m.value.kind, value)?, + None => { + return Err(PyValueError::new_err(format!( + "Param '{param_name}' not found" + ))) + } + }; + + let builder = value + .pack_into_chain(&contract.abi_version) + .handle_runtime_error()?; + + map.set_builder(serialize_state_init_data_key(param.key), &builder) + .handle_runtime_error()?; + } + } + + map.write_to_new_cell() + .and_then(ton_types::BuilderData::into_cell) + .handle_runtime_error() + .map(Cell) +} + +fn decode_init_data_dict<'a>( + py: Python<'a>, + contract: &ton_abi::Contract, + data: &Cell, +) -> PyResult<(Option, &'a PyDict)> { + let pubkey = { + let map = ton_types::HashmapE::with_hashmap( + ton_abi::Contract::DATA_MAP_KEYLEN, + data.0.reference(0).ok(), + ); + + let value = map + .get(serialize_state_init_data_key(0)) + .handle_value_error()?; + match value { + Some(mut value) => { + let pubkey = value.get_next_hash().handle_value_error()?; + if pubkey.is_zero() { + None + } else { + Some(PublicKey( + ed25519_dalek::PublicKey::from_bytes(pubkey.as_slice()) + .handle_value_error()?, + )) + } + } + None => None, + } + }; + + let data = ton_types::SliceData::load_cell_ref(&data.0).handle_value_error()?; + + let tokens = contract.decode_init_data(data).handle_value_error()?; + Ok((pubkey, convert_tokens(py, tokens)?)) +} + +fn update_init_data_plain( + contract: &ton_abi::Contract, + data: &PyDict, + mut public_key: Option<&PublicKey>, + existing_data: Cell, +) -> PyResult { + let data_slice = ton_types::SliceData::load_cell(existing_data.0).handle_value_error()?; + let old_values = ton_abi::TokenValue::decode_params( + &contract.fields, + data_slice, + &contract.abi_version, + false, + ) + .handle_value_error()?; + + let mut result = Vec::with_capacity(old_values.len()); + for (field, old_value) in std::iter::zip(&contract.fields, old_values) { + let mut token = match data.get_item(&field.name) { + Some(value) => Some(parse_token(&field.kind, value)?), + None => None, + }; + + if field.name == PUBKEY_FIELD { + if let Some(pubkey) = public_key.take() { + // Overwrite pubkey if specified. + token = Some(ton_abi::TokenValue::Uint(ton_abi::Uint { + size: 256, + number: num_bigint::BigUint::from_bytes_be(pubkey.0.as_bytes()), + })); + } + } + + result.push(if let Some(token) = token { + if !contract.init_fields.contains(&field.name) { + return Err(PyValueError::new_err(format!( + "Unexpected '{}' init data param", + field.name + ))); + } + + ton_abi::Token::new(&field.name, token) + } else { + old_value + }); + } + + if public_key.is_some() { + return Err(PyValueError::new_err("Explicit pubkey was not used")); + } + + ton_abi::TokenValue::pack_values_into_chain(&result, Vec::new(), &contract.abi_version) + .and_then(ton_types::BuilderData::into_cell) + .map(Cell) + .handle_runtime_error() +} + +fn encode_init_data_plain( + contract: &ton_abi::Contract, + data: &PyDict, + mut public_key: Option<&PublicKey>, +) -> PyResult { + let mut init_fields = HashMap::default(); + for field in &contract.fields { + if let Some(value) = data.get_item(&field.name) { + init_fields.insert(field.name.clone(), parse_token(&field.kind, value)?); + } + } + + if contract.init_fields.contains(PUBKEY_FIELD) { + if let Some(pubkey) = public_key.take() { + // Overwrite pubkey if specified. + init_fields.insert( + PUBKEY_FIELD.to_owned(), + ton_abi::TokenValue::Uint(ton_abi::Uint { + size: 256, + number: num_bigint::BigUint::from_bytes_be(pubkey.0.as_bytes()), + }), + ); + } + } + + if public_key.is_some() { + return Err(PyValueError::new_err("Explicit pubkey was not used")); + } + + contract + .encode_storage_fields(init_fields) + .and_then(ton_types::BuilderData::into_cell) + .map(Cell) + .handle_runtime_error() +} + +fn decode_init_data_plain<'a>( + py: Python<'a>, + contract: &ton_abi::Contract, + data: &Cell, +) -> PyResult<(Option, &'a PyDict)> { + use nt::abi::UnpackAbi; + + let data_slice = ton_types::SliceData::load_cell(data.0.clone()).handle_value_error()?; + let mut values = ton_abi::TokenValue::decode_params( + &contract.fields, + data_slice, + &contract.abi_version, + false, + ) + .handle_value_error()?; + + values.retain(|item| contract.init_fields.contains(&item.name)); + + let pubkey = 'pubkey: { + if let Some(pubkey) = values.iter().find(|item| item.name == PUBKEY_FIELD) { + let value: ton_types::UInt256 = pubkey.clone().unpack().handle_value_error()?; + if !value.is_zero() { + break 'pubkey Some(PublicKey( + ed25519_dalek::PublicKey::from_bytes(value.as_slice()).handle_value_error()?, + )); + } + } + + None + }; + + Ok((pubkey, convert_tokens(py, values)?)) +} + +const PUBKEY_FIELD: &str = "_pubkey"; + struct SharedContractAbi { contract: ton_abi::Contract, functions: FastHashMap, events: FastHashMap, + getters: FastHashMap, } #[derive(FromPyObject)] @@ -454,10 +645,10 @@ impl FunctionAbi { self.0.output_id } - fn with_args(&self, py: Python<'_>, args: &PyDict) -> FunctionAbiWithArgs { + fn with_args(&self, py: Python<'_>, input: &PyDict) -> FunctionAbiWithArgs { FunctionAbiWithArgs { abi: self.clone(), - args: args.into_py(py), + args: input.into_py(py), } } @@ -486,7 +677,14 @@ impl FunctionAbi { let responsible = matches!(responsible, Some(true)); let execution_output = self .0 - .run_local_ext(clock, account_state.0.clone(), &input, responsible, &config) + .run_local_ext( + clock, + account_state.0.clone(), + &input, + responsible, + &config, + &[], + ) .handle_runtime_error()?; Ok(ExecutionOutput { @@ -625,7 +823,7 @@ impl FunctionAbi { let input = self .0 - .decode_input(in_msg_body, in_msg.is_internal()) + .decode_input(in_msg_body, in_msg.is_internal(), false) .handle_runtime_error()?; let output = self.0.parse(tx).handle_runtime_error()?; @@ -644,12 +842,9 @@ impl FunctionAbi { ) -> PyResult<&'a PyDict> { let abi = self.0.as_ref(); let body = ton_types::SliceData::load_cell_ref(&message_body.0).handle_value_error()?; - let values = if matches!(allow_partial, Some(true)) { - abi.decode_input_partial(body, internal) - } else { - abi.decode_input(body, internal) - } - .handle_runtime_error()?; + let values = abi + .decode_input(body, internal, matches!(allow_partial, Some(true))) + .handle_runtime_error()?; convert_tokens(py, values) } @@ -906,6 +1101,107 @@ impl EventAbi { } } +#[derive(Clone)] +#[pyclass(subclass)] +pub struct GetterAbi(Arc); + +#[pymethods] +impl GetterAbi { + #[getter] + fn name(&self) -> String { + self.0.name.clone() + } + + // TODO: Use function id. + #[getter] + fn method_id(&self) -> u32 { + self.0.name.as_str().as_getter_method_id() + } + + // TODO: Use function id. + fn call<'a>( + &self, + py: Python<'a>, + account_state: &AccountState, + input: &PyDict, + clock: Option<&Clock>, + config: Option, + ) -> PyResult { + let input = parse_tokens(&self.0.inputs, input)?; + let clock = match clock { + Some(clock) => clock.as_ref(), + None => &nt::utils::SimpleClock, + }; + + let input = input + .into_iter() + .map(|item| token_to_stack_item(item.value)) + .collect::>>()?; + + let config = match &config { + Some(config) => nt::abi::BriefBlockchainConfig::from(config.as_ref()), + None => nt::abi::BriefBlockchainConfig::default(), + }; + + let ctx = nt::abi::ExecutionContext { + clock, + account_stuff: &account_state.0, + // TODO: Add support for libraries + libraries: &[], + } + .run_getter_ext(self.0.name.as_str(), &input, &config, &Default::default()) + .handle_runtime_error()?; + + drop(input); + + Ok(ExecutionOutput { + exit_code: ctx.exit_code, + output: (ctx.exit_code == 0) + .then(|| { + if ctx.stack.len() != self.0.outputs.len() { + return Err(PyRuntimeError::new_err("Output stack size mismatch")); + } + + let outputs = self + .0 + .outputs + .iter() + .zip(ctx.stack) + .map(|(param, value)| { + let value = stack_item_to_token(¶m.kind, &value)?; + Ok(ton_abi::Token::new(¶m.name, value)) + }) + .collect::>>()?; + + Ok(convert_tokens(py, outputs)?.into_py(py)) + }) + .transpose()?, + }) + } + + fn __repr__(&self) -> String { + format!( + "", + self.0.name, self.0.input_id + ) + } + + fn __hash__(&self) -> u64 { + self.0.input_id as u64 + } + + fn __richcmp__(&self, other: &Self, op: pyo3::basic::CompareOp) -> bool { + match op { + pyo3::basic::CompareOp::Eq => self.0.eq(&other.0), + pyo3::basic::CompareOp::Ne => !self.0.eq(&other.0), + pyo3::basic::CompareOp::Lt => self.0.input_id < other.0.input_id, + pyo3::basic::CompareOp::Le => self.0.input_id <= other.0.input_id, + pyo3::basic::CompareOp::Gt => self.0.input_id > other.0.input_id, + pyo3::basic::CompareOp::Ge => self.0.input_id >= other.0.input_id, + } + } +} + #[pyclass(extends = Message)] pub struct SignedExternalMessage { pub expire_at: u32, @@ -1022,13 +1318,13 @@ impl UnsignedExternalMessage { self.state_init = state_init; } - fn sign( + fn sign<'a>( &self, - py: Python<'_>, + py: Python<'a>, keypair: &KeyPair, - signature_id: Option, + context: &'a PyAny, ) -> PyResult> { - self.fill_body(py, self.body.sign(keypair, signature_id)?) + self.fill_body(py, self.body.sign(keypair, context)?) } fn with_signature( @@ -1084,8 +1380,8 @@ impl UnsignedBody { self.expire_at } - fn sign(&self, keypair: &KeyPair, signature_id: Option) -> PyResult { - let signature = keypair.sign_raw(self.hash.as_ref(), signature_id); + fn sign(&self, keypair: &KeyPair, context: &PyAny) -> PyResult { + let signature = keypair.sign_raw(self.hash.as_ref(), context)?; self.fill_signature(Some(signature.0.as_ref())) } @@ -1163,6 +1459,7 @@ define_abi_types! { ton_abi::ParamType::Map(key_type, value_type) }, AbiAddress = | | ton_abi::ParamType::Address, + AbiAddressStd = | | ton_abi::ParamType::AddressStd, AbiBytes = | | ton_abi::ParamType::Bytes, AbiFixedBytes = |len: usize| ton_abi::ParamType::FixedBytes(len), AbiString = | | ton_abi::ParamType::String, @@ -1346,6 +1643,17 @@ fn parse_token(param: &ton_abi::ParamType, value: &PyAny) -> PyResult ton_block::MsgAddress::AddrVar(addr), }) } + ton_abi::ParamType::AddressStd => { + let Address(addr) = value.extract::
()?; + ton_abi::TokenValue::Address(match addr { + ton_block::MsgAddressInt::AddrStd(addr) => ton_block::MsgAddress::AddrStd(addr), + ton_block::MsgAddressInt::AddrVar(_) => { + return Err(PyValueError::new_err( + "Expected `addr_std` instead of `addr_var`", + )) + } + }) + } ton_abi::ParamType::Bytes => { let bytes = value.extract::<&[u8]>()?; ton_abi::TokenValue::Bytes(bytes.to_vec()) @@ -1448,7 +1756,7 @@ fn parse_map_entry_token( Ok((key, value)) } -pub fn convert_tokens(py: Python, tokens: Vec) -> PyResult<&PyDict> { +pub fn convert_tokens(py: Python<'_>, tokens: Vec) -> PyResult<&PyDict> { let result = PyDict::new(py); for token in tokens { result.set_item(&token.name, convert_token(py, token.value)?)?; @@ -1482,6 +1790,7 @@ fn convert_token(py: Python, value: ton_abi::TokenValue) -> PyResult { PyList::new(py, items).to_object(py) } ton_abi::TokenValue::Address(addr) => convert_addr_token(py, addr)?, + ton_abi::TokenValue::AddressStd(addr) => convert_addr_token(py, addr)?, ton_abi::TokenValue::Bytes(bytes) | ton_abi::TokenValue::FixedBytes(bytes) => { PyBytes::new(py, &bytes).to_object(py) } @@ -1549,3 +1858,580 @@ pub fn default_headers( (expire_at, header) } + +#[pyclass] +pub struct TupleReader { + items: Vec, +} + +impl TupleReader { + fn from_items(mut items: Vec) -> Self { + items.reverse(); + Self { items } + } + + fn pop_impl(&mut self) -> PyResult { + match self.items.pop() { + Some(item) => Ok(item), + None => Err(PyEOFError::new_err("Tuple is empty")), + } + } + + fn read_as_lisp_list<'a>( + py: Python<'a>, + mut tail: Option<&[ton_vm::stack::StackItem]>, + ) -> PyResult> { + let mut result = Vec::new(); + while let Some(t) = tail { + let [next, value] = t else { + return Err(PyValueError::new_err(format!( + "Expected tuple of two items, got {}", + t.len() + ))); + }; + + result.push(convert_stack_item(py, value)?); + tail = match next { + ton_vm::stack::StackItem::None => None, + ton_vm::stack::StackItem::Tuple(items) => Some(items.as_slice()), + _ => return Err(PyTypeError::new_err("Expected tail to be null or tuple")), + } + } + + Ok(result) + } +} + +#[pymethods] +impl TupleReader { + #[new] + pub fn new(items: &PySequence) -> PyResult { + parse_stack_items(items).map(Self::from_items) + } + + #[getter] + pub fn remaining(&self) -> usize { + self.items.len() + } + + pub fn into_inner<'a>(&self, py: Python<'a>) -> PyResult<&'a PyTuple> { + let mut items = self.items.clone(); + items.reverse(); + convert_stack_items(py, &items) + } + + pub fn peek<'a>(&self, py: Python<'a>) -> PyResult { + match self.items.last() { + Some(item) => convert_stack_item(py, item), + None => Err(PyEOFError::new_err("Tuple is empty")), + } + } + + pub fn pop<'a>(&mut self, py: Python<'a>) -> PyResult { + match self.items.pop() { + Some(item) => convert_stack_item(py, &item), + None => Err(PyEOFError::new_err("Tuple is empty")), + } + } + + pub fn skip(&mut self, n: usize) -> PyResult<()> { + if let Some(remaining) = self.items.len().checked_sub(n) { + self.items.truncate(remaining); + Ok(()) + } else { + self.items.clear(); + Err(PyEOFError::new_err( + "Tuple does not contain enough items to skip", + )) + } + } + + pub fn read_int(&mut self) -> PyResult { + if let ton_vm::stack::StackItem::Integer(ref int) = self.pop_impl()? { + if let Ok(int) = int.take_value_of(move |int| Some(int.clone())) { + return Ok(int); + } + } + Err(PyTypeError::new_err("Not a number")) + } + + pub fn read_int_opt(&mut self) -> PyResult> { + match self.pop_impl()? { + ton_vm::stack::StackItem::None => return Ok(None), + ton_vm::stack::StackItem::Integer(ref int) => { + if let Ok(int) = int.take_value_of(move |int| Some(int.clone())) { + return Ok(Some(int)); + } + } + _ => {} + } + Err(PyTypeError::new_err("Not a number")) + } + + pub fn read_bool(&mut self) -> PyResult { + Ok(self.read_int()?.is_zero()) + } + + pub fn read_bool_opt(&mut self) -> PyResult> { + self.read_int_opt() + .map(|item| item.as_ref().map(Zero::is_zero)) + } + + pub fn read_cell(&mut self) -> PyResult { + if let ton_vm::stack::StackItem::Cell(ref cell) = self.pop_impl()? { + return Ok(Cell(cell.clone())); + } + Err(PyTypeError::new_err("Not a cell")) + } + + pub fn read_cell_opt(&mut self) -> PyResult> { + match self.pop_impl()? { + ton_vm::stack::StackItem::None => Ok(None), + ton_vm::stack::StackItem::Cell(ref cell) => Ok(Some(Cell(cell.clone()))), + _ => Err(PyTypeError::new_err("Not a cell")), + } + } + + pub fn read_address(&mut self) -> PyResult
{ + let mut cs = ton_types::SliceData::load_cell(self.read_cell()?.0).handle_value_error()?; + let mut addr = ton_block::MsgAddressInt::default(); + addr.read_from(&mut cs).handle_value_error()?; + Ok(Address(addr)) + } + + pub fn read_address_opt(&mut self) -> PyResult> { + let Some(cell) = self.read_cell_opt()? else { + return Ok(None); + }; + let mut cs = ton_types::SliceData::load_cell(cell.0).handle_value_error()?; + let mut addr = ton_block::MsgAddressIntOrNone::default(); + addr.read_from(&mut cs).handle_value_error()?; + Ok(match addr { + ton_block::MsgAddressIntOrNone::None => None, + ton_block::MsgAddressIntOrNone::Some(addr) => Some(Address(addr)), + }) + } + + pub fn read_tuple(&mut self) -> PyResult { + if let ton_vm::stack::StackItem::Tuple(ref items) = self.pop_impl()? { + return Ok(Self::from_items(items.as_slice().to_vec())); + } + Err(PyTypeError::new_err("Not a tuple")) + } + + pub fn read_tuple_opt(&mut self) -> PyResult> { + match self.pop_impl()? { + ton_vm::stack::StackItem::None => Ok(None), + ton_vm::stack::StackItem::Tuple(ref items) => { + Ok(Some(Self::from_items(items.as_slice().to_vec()))) + } + _ => Err(PyTypeError::new_err("Not a tuple")), + } + } + + pub fn read_lisp_list_direct<'a>(&mut self, py: Python<'a>) -> PyResult> { + let items = std::mem::take(&mut self.items); + if let [ton_vm::stack::StackItem::None] = items.as_slice() { + return Ok(Vec::new()); + } + Self::read_as_lisp_list(py, Some(&items)) + } + + pub fn read_lisp_list<'a>(&mut self, py: Python<'a>) -> PyResult> { + let items = self.read_tuple_opt()?; + Self::read_as_lisp_list(py, items.as_ref().map(|reader| reader.items.as_slice())) + } + + pub fn read_buffer<'a>(&mut self, py: Python<'a>) -> PyResult<&'a PyBytes> { + let cell = self.read_cell()?; + match parse_bytes_from_cell(py, cell.0) { + Some(bytes) => Ok(bytes), + None => Err(PyTypeError::new_err("Not a buffer")), + } + } + + pub fn read_buffer_opt<'a>(&mut self, py: Python<'a>) -> PyResult> { + let Some(cell) = self.read_cell_opt()? else { + return Ok(None); + }; + match parse_bytes_from_cell(py, cell.0) { + Some(bytes) => Ok(Some(bytes)), + None => Err(PyTypeError::new_err("Not a buffer")), + } + } + + pub fn read_str(&mut self) -> PyResult { + let cell = self.read_cell()?; + parse_string_from_cell(cell.0) + } + + pub fn read_str_opt(&mut self) -> PyResult> { + let Some(cell) = self.read_cell_opt()? else { + return Ok(None); + }; + parse_string_from_cell(cell.0).map(Some) + } +} + +fn parse_bytes_from_cell<'a>(py: Python<'a>, cell: ton_types::Cell) -> Option<&'a PyBytes> { + if cell.bit_length().is_multiple_of(8) && cell.references_count() == 0 { + let mut cs = ton_types::SliceData::load_cell(cell).ok()?; + let bytes = cs.get_next_bytes(cs.remaining_bits() / 8).ok()?; + Some(PyBytes::new(py, &bytes)) + } else { + None + } +} + +fn parse_string_from_cell(mut cell: ton_types::Cell) -> PyResult { + let mut buffer = Vec::new(); + + let mut cs = ton_types::SliceData::load_cell(cell).handle_value_error()?; + loop { + let bit_len = cs.remaining_bits(); + if !bit_len.is_multiple_of(8) { + return Err(PyValueError::new_err("Invalid string length")); + } + let refs = cs.remaining_references(); + if !(0..=1).contains(&refs) { + return Err(PyValueError::new_err("Invalid number of refs")); + } + + let byte_count = bit_len / 8; + buffer.reserve(byte_count); + for _ in 0..byte_count { + buffer.push(cs.get_next_byte().unwrap()); + } + + if refs > 0 { + cell = cs.checked_drain_reference().handle_value_error()?; + cs = ton_types::SliceData::load_cell(cell).handle_value_error()?; + } else { + break; + } + } + + Ok(String::from_utf8_lossy(&buffer).into_owned()) +} + +fn bytes_to_cell(bytes: &[u8]) -> PyResult { + let bit_len = bytes.len() * 8; + ton_types::BuilderData::with_raw(bytes.into(), bit_len) + .handle_value_error()? + .into_cell() + .handle_value_error() +} + +fn bytes_to_cell_chain(bytes: &[u8]) -> PyResult { + const CHUNK_LEN: usize = 127; + + let (chunks, rem) = bytes.as_chunks::(); + + let mut child = None; + if !rem.is_empty() { + child = Some(bytes_to_cell(rem)?); + } + + for chunk in chunks.iter().rev() { + let mut builder = + ton_types::BuilderData::with_raw(chunk.as_slice().into(), chunks.len() * 8) + .handle_value_error()?; + if let Some(child) = child.take() { + builder + .checked_append_reference(child) + .handle_value_error()?; + } + child = Some(builder.into_cell().handle_value_error()?); + } + + Ok(child.unwrap_or_default()) +} + +fn token_to_stack_item(value: ton_abi::TokenValue) -> PyResult { + use ton_vm::stack::integer::IntegerData; + use ton_vm::stack::StackItem; + + Ok(match value { + ton_abi::TokenValue::Uint(value) => { + StackItem::integer(IntegerData::from(value.number).handle_value_error()?) + } + ton_abi::TokenValue::Int(value) => { + StackItem::integer(IntegerData::from(value.number).handle_value_error()?) + } + ton_abi::TokenValue::VarInt(_, value) => { + StackItem::integer(IntegerData::from(value).handle_value_error()?) + } + ton_abi::TokenValue::VarUint(_, value) => { + StackItem::integer(IntegerData::from(value).handle_value_error()?) + } + ton_abi::TokenValue::Bool(value) => StackItem::boolean(value), + ton_abi::TokenValue::Tuple(tokens) => StackItem::tuple( + tokens + .into_iter() + .map(|token| token_to_stack_item(token.value)) + .collect::>()?, + ), + ton_abi::TokenValue::Array(_, values) | ton_abi::TokenValue::FixedArray(_, values) => { + StackItem::tuple( + values + .into_iter() + .map(|value| token_to_stack_item(value)) + .collect::>()?, + ) + } + ton_abi::TokenValue::Cell(value) => StackItem::cell(value), + ton_abi::TokenValue::Address(value) | ton_abi::TokenValue::AddressStd(value) => { + StackItem::Slice( + ton_types::SliceData::load_cell(value.serialize().handle_value_error()?) + .handle_value_error()?, + ) + } + ton_abi::TokenValue::Bytes(value) | ton_abi::TokenValue::FixedBytes(value) => { + StackItem::Cell(bytes_to_cell(&value)?) + } + ton_abi::TokenValue::String(value) => { + StackItem::Cell(bytes_to_cell_chain(value.as_bytes())?) + } + ton_abi::TokenValue::Token(value) => StackItem::integer(value.as_u128().into()), + ton_abi::TokenValue::Time(value) => StackItem::integer(value.into()), + ton_abi::TokenValue::Expire(value) => StackItem::integer(value.into()), + ton_abi::TokenValue::PublicKey(value) => { + if let Some(public_key) = value { + StackItem::integer( + IntegerData::from(num_bigint::BigUint::from_bytes_be(public_key.as_bytes())) + .handle_value_error()?, + ) + } else { + StackItem::None + } + } + ton_abi::TokenValue::Optional(_, value) => match value { + Some(value) => token_to_stack_item(*value)?, + None => StackItem::None, + }, + ton_abi::TokenValue::Ref(value) => token_to_stack_item(*value)?, + ton_abi::TokenValue::Map { .. } => { + return Err(PyValueError::new_err( + "HashmapE is not supported by getters", + )) + } + }) +} + +fn stack_item_to_token( + param: &ton_abi::ParamType, + value: &ton_vm::stack::StackItem, +) -> PyResult { + use ton_abi::{Int, ParamType, TokenValue, Uint}; + use ton_vm::stack::StackItem; + + if let ton_abi::ParamType::Optional(param) = param { + let value = if let StackItem::None = value { + None + } else { + Some(Box::new(stack_item_to_token(param, value)?)) + }; + return Ok(ton_abi::TokenValue::Optional(*param.clone(), value)); + } else if let ton_abi::ParamType::Ref(param) = param { + let value = Box::new(stack_item_to_token(param, value)?); + return Ok(ton_abi::TokenValue::Ref(value)); + } + + Ok(match value { + StackItem::None => match param { + ParamType::Map(key, value) => { + TokenValue::Map(*key.clone(), *value.clone(), Default::default()) + } + _ => return Err(PyValueError::new_err("Unexpected null in getter output")), + }, + StackItem::Integer(value) => { + let value = + ton_vm::stack::integer::utils::process_value(&value, |bigint| Ok(bigint.clone())) + .handle_value_error()?; + + match param { + ParamType::Uint(s) => TokenValue::Uint(Uint { + number: value.try_into().handle_value_error()?, + size: *s, + }), + ParamType::Int(s) => TokenValue::Int(Int { + number: value, + size: *s, + }), + ParamType::VarUint(s) => { + TokenValue::VarUint(*s, value.try_into().handle_value_error()?) + } + ParamType::VarInt(s) => TokenValue::VarInt(*s, value), + ParamType::Bool => TokenValue::Bool(!value.is_zero()), + ParamType::Time => TokenValue::Time(value.try_into().handle_value_error()?), + ParamType::Expire => TokenValue::Expire(value.try_into().handle_value_error()?), + _ => return Err(PyValueError::new_err("Unexpected integer in getter output")), + } + } + StackItem::Tuple(values) => match param { + ParamType::Tuple(params) => { + if params.len() != values.len() { + return Err(PyValueError::new_err( + "Tuple items mismatch in getter output", + )); + } + + let tokens = params + .iter() + .zip(values.iter()) + .map(|(param, value)| { + let value = stack_item_to_token(¶m.kind, value)?; + Ok(ton_abi::Token::new(¶m.name, value)) + }) + .collect::>>()?; + + TokenValue::Tuple(tokens) + } + ParamType::Array(param) => { + let tokens = values + .iter() + .map(|value| stack_item_to_token(param, value)) + .collect::>>()?; + + TokenValue::Array(*param.clone(), tokens) + } + ParamType::FixedArray(param, size) => { + if values.len() != *size { + return Err(PyValueError::new_err("Fixed array size mismatch")); + } + + let tokens = values + .iter() + .map(|value| stack_item_to_token(param, value)) + .collect::>>()?; + + TokenValue::FixedArray(*param.clone(), tokens) + } + _ => return Err(PyValueError::new_err("Unexpected tuple in getter output")), + }, + // FIXME: Properly hande all possible type combinations. + StackItem::Cell(value) => { + let slice = ton_types::SliceData::load_cell(value.clone()).handle_value_error()?; + read_token_value(¶m, slice).handle_value_error()? + } + StackItem::Slice(value) => read_token_value(param, value.clone()).handle_value_error()?, + StackItem::Builder(arc) => { + let cell = arc.as_ref().clone().into_cell().handle_value_error()?; + let slice = ton_types::SliceData::load_cell(cell).handle_value_error()?; + + read_token_value(param, slice).handle_value_error()? + } + StackItem::Continuation(_) => { + return Err(PyValueError::new_err( + "Unexpected continuation in getter output", + )) + } + }) +} + +fn read_token_value( + param: &ton_abi::ParamType, + slice: ton_types::SliceData, +) -> Result { + ton_abi::TokenValue::read_from( + ¶m, + slice.into(), + true, + &ton_abi::contract::ABI_VERSION_2_7, + true, + ) + .map(|(value, _)| value) +} + +pub fn parse_stack_items(value: &PySequence) -> PyResult> { + let mut result = Vec::with_capacity(value.len()?); + for item in value.iter()? { + result.push(parse_stack_item(item?)?); + } + Ok(result) +} + +// TODO: There must be a better way of doing this. +pub fn parse_stack_item(value: &PyAny) -> PyResult { + use pyo3::types::*; + use pyo3::PyTypeInfo; + use ton_vm::stack::integer::IntegerData; + use ton_vm::stack::StackItem; + + Ok(if value.is_none() { + StackItem::None + } else if PyBool::is_type_of(value) { + StackItem::Integer(Arc::new(if value.is_true()? { + IntegerData::minus_one() + } else { + IntegerData::zero() + })) + } else if PyInt::is_type_of(value) { + StackItem::Integer(Arc::new( + IntegerData::from(value.extract::()?).handle_value_error()?, + )) + } else if let Ok(tokens) = value.extract::>() { + StackItem::Integer(Arc::new(IntegerData::from_i128(tokens.0))) + } else if let Ok(pubkey) = value.extract::>() { + StackItem::Integer(Arc::new(IntegerData::from_unsigned_bytes_be( + pubkey.0.as_bytes(), + ))) + } else if let Ok(builder) = value.extract::>() { + StackItem::Builder(Arc::new(builder.builder.clone())) + } else if let Ok(cell) = value.extract::>() { + StackItem::Cell(cell.0.clone()) + } else if let Ok(slice) = value.extract::>() { + StackItem::Slice(slice.slice.clone()) + } else if let Ok(items) = ::try_from(value) { + StackItem::Tuple(Arc::new(parse_stack_items(items)?)) + } else { + return Err(PyValueError::new_err( + "Provided value cannot be used as a stack item", + )); + }) +} + +pub fn convert_stack_items<'py>( + py: Python<'py>, + items: &[ton_vm::stack::StackItem], +) -> PyResult<&'py PyTuple> { + let mut result = Vec::with_capacity(items.len()); + for item in items { + result.push(convert_stack_item(py, item)?); + } + Ok(PyTuple::new(py, result)) +} + +pub fn convert_stack_item<'py>( + py: Python<'py>, + item: &ton_vm::stack::StackItem, +) -> PyResult { + Ok(match item { + ton_vm::stack::StackItem::None => py_none(), + ton_vm::stack::StackItem::Builder(builder) => CellBuilder { + builder: builder.as_ref().clone(), + is_exotic: false, + } + .into_py(py), + ton_vm::stack::StackItem::Cell(cell) => Cell(cell.clone()).into_py(py), + ton_vm::stack::StackItem::Continuation(_cell) => { + todo!() + } + // TODO: Decide what to do with NaN. + ton_vm::stack::StackItem::Integer(int) => int + .take_value_of(move |int| Some(int.clone().into_py(py))) + .unwrap_or_else(move |_| NaN.into_py(py)), + ton_vm::stack::StackItem::Slice(slice) => CellSlice { + cell: Cell(slice.cell().clone()), + slice: slice.clone(), + } + .into_py(py), + ton_vm::stack::StackItem::Tuple(items) => { + convert_stack_items(py, items.as_ref())?.to_object(py) + } + }) +} + +#[derive(Clone, Copy)] +#[pyclass] +pub struct NaN; diff --git a/src/crypto.rs b/src/crypto.rs index dd7fcb2..be827b7 100644 --- a/src/crypto.rs +++ b/src/crypto.rs @@ -1,11 +1,92 @@ +use std::borrow::Cow; + use pyo3::exceptions::*; use pyo3::prelude::*; use pyo3::types::PyBytes; use rand::Rng; -use sha2::Digest; +use sha2::{Digest, Sha256}; use crate::util::{Encoding, HandleError}; +const CAP_SIGNATURE_WITH_ID: u64 = 0x4000000; +const CAP_SIGNATURE_DOMAIN: u64 = 0x800000000; + +#[derive(Debug, Clone, Copy)] +#[pyclass] +pub struct SignatureContext { + pub global_id: i32, + pub capabilities: u64, +} + +impl SignatureContext { + pub fn apply_from_arg<'a, 'py>(data: &'a [u8], context: &'py PyAny) -> PyResult> { + Ok(if context.is_none() { + Cow::Borrowed(data) + } else if let Ok(global_id) = context.extract::() { + ton_abi::extend_signature_with_id(data, Some(global_id)) + } else { + let context = context.extract::()?; + context.apply_impl(data) + }) + } + + pub fn apply_impl<'a>(&self, data: &'a [u8]) -> Cow<'a, [u8]> { + if self.capabilities & CAP_SIGNATURE_WITH_ID == 0 { + return Cow::Borrowed(data); + } + + if self.capabilities & CAP_SIGNATURE_DOMAIN != 0 { + let mut result = Vec::with_capacity(32 + data.len()); + result.extend_from_slice(&0x71b34ee1u32.to_le_bytes()); // L2 variant tl tag + result.extend_from_slice(&self.global_id.to_le_bytes()); + let hash: [u8; 32] = Sha256::digest(&result).into(); + + result.clear(); + result.extend_from_slice(&hash); + result.extend_from_slice(data); + Cow::Owned(result) + } else { + let mut result = Vec::with_capacity(4 + data.len()); + result.extend_from_slice(&self.global_id.to_be_bytes()); + result.extend_from_slice(data); + Cow::Owned(result) + } + } +} + +#[pymethods] +impl SignatureContext { + #[new] + pub fn new(global_id: i32, capabilities: u64) -> Self { + Self { + global_id, + capabilities, + } + } + + #[getter] + fn global_id(&self) -> i32 { + self.global_id + } + + #[getter] + fn capabilities(&self) -> u64 { + self.capabilities + } + + pub fn apply<'a>(&self, py: Python<'a>, data: &[u8]) -> &'a PyBytes { + let res = self.apply_impl(data); + PyBytes::new(py, &res) + } + + fn __repr__(&self) -> String { + format!( + "SignatureContext(global_id={}, capabilities={})", + self.global_id, self.capabilities + ) + } +} + #[pyclass] pub struct PublicKey(pub ed25519_dalek::PublicKey); @@ -39,15 +120,16 @@ impl PublicKey { encoding.decode_pubkey(value).map(Self) } - pub fn check_signature( + pub fn check_signature<'a>( &self, data: &[u8], signature: &Signature, - signature_id: Option, - ) -> bool { + context: &'a PyAny, + ) -> PyResult { use ed25519_dalek::Verifier; - let data = ton_abi::extend_signature_with_id(data, signature_id); - self.0.verify(&data, &signature.0).is_ok() + + let data = SignatureContext::apply_from_arg(data, context)?; + Ok(self.0.verify(&data, &signature.0).is_ok()) } pub fn encode(&self, encoding: Option<&str>) -> PyResult { @@ -107,31 +189,31 @@ impl KeyPair { PublicKey(self.0.public) } - pub fn sign(&self, data: &[u8], signature_id: Option) -> Signature { + pub fn sign(&self, data: &[u8], context: &PyAny) -> PyResult { use ed25519_dalek::Signer; use sha2::Digest; let data = sha2::Sha256::digest(data); - let data = ton_abi::extend_signature_with_id(&data, signature_id); - Signature(self.0.sign(&data)) + let data = SignatureContext::apply_from_arg(&data, context)?; + Ok(Signature(self.0.sign(&data))) } - pub fn sign_raw(&self, data: &[u8], signature_id: Option) -> Signature { + pub fn sign_raw(&self, data: &[u8], context: &PyAny) -> PyResult { use ed25519_dalek::Signer; - let data = ton_abi::extend_signature_with_id(data, signature_id); - Signature(self.0.sign(&data)) + let data = SignatureContext::apply_from_arg(&data, context)?; + Ok(Signature(self.0.sign(&data))) } pub fn check_signature( &self, data: &[u8], signature: &Signature, - signature_id: Option, - ) -> bool { + context: &PyAny, + ) -> PyResult { use ed25519_dalek::Verifier; - let data = ton_abi::extend_signature_with_id(data, signature_id); - self.0.public.verify(&data, &signature.0).is_ok() + let data = SignatureContext::apply_from_arg(&data, context)?; + Ok(self.0.public.verify(&data, &signature.0).is_ok()) } fn __hash__(&self) -> u64 { diff --git a/src/lib.rs b/src/lib.rs index 232c6b4..c19fe86 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -64,6 +64,7 @@ fn nekoton(_py: Python, m: &PyModule) -> PyResult<()> { m.add_class::()?; m.add_class::()?; m.add_class::()?; + m.add_class::()?; m.add_class::()?; m.add_class::()?; m.add_class::()?; @@ -83,14 +84,18 @@ fn nekoton(_py: Python, m: &PyModule) -> PyResult<()> { m.add_class::()?; m.add_class::()?; m.add_class::()?; + m.add_class::()?; m.add_class::()?; m.add_class::()?; m.add_class::()?; m.add_class::()?; m.add_class::()?; m.add_class::()?; + m.add_class::()?; + m.add_class::()?; // Crypto + m.add_class::()?; m.add_class::()?; m.add_class::()?; m.add_class::()?; diff --git a/src/models.rs b/src/models.rs index 796db26..0fe9123 100644 --- a/src/models.rs +++ b/src/models.rs @@ -8,7 +8,7 @@ use ton_block::{Deserializable, GetRepresentationHash, Serializable}; use ton_types::IBitstring; use crate::abi::{convert_tokens, parse_tokens, AbiParam, AbiVersion}; -use crate::crypto::{PublicKey, Signature}; +use crate::crypto::{PublicKey, Signature, SignatureContext}; use crate::util::{make_hasher, py_none, Encoding, HandleError}; #[derive(Clone)] @@ -39,6 +39,14 @@ impl BlockchainConfig { } } + #[getter] + fn signature_context(&self) -> SignatureContext { + SignatureContext { + global_id: self.0.global_id(), + capabilities: self.0.capabilites(), + } + } + #[getter] fn global_version(&self) -> u32 { self.0.global_version() @@ -222,18 +230,8 @@ impl StorageUsed { self.0.bits.as_u64() } - #[getter] - fn public_cells(&self) -> u64 { - self.0.public_cells.as_u64() - } - fn __repr__(&self) -> String { - format!( - "", - self.cells(), - self.bits(), - self.public_cells() - ) + format!("", self.cells(), self.bits(),) } } @@ -950,19 +948,20 @@ impl Message { } #[getter] - fn created_at(&self) -> Option { + fn created_at(&self) -> u32 { match self.data.header() { - ton_block::CommonMsgInfo::IntMsgInfo(x) => Some(x.created_at.as_u32()), - ton_block::CommonMsgInfo::ExtOutMsgInfo(x) => Some(x.created_at.as_u32()), - ton_block::CommonMsgInfo::ExtInMsgInfo(_) => None, + ton_block::CommonMsgInfo::IntMsgInfo(x) => x.created_at.as_u32(), + ton_block::CommonMsgInfo::ExtOutMsgInfo(x) => x.created_at.as_u32(), + ton_block::CommonMsgInfo::ExtInMsgInfo(_) => 0, } } + #[getter] - fn created_lt(&self) -> Option { + fn created_lt(&self) -> u64 { match self.data.header() { - ton_block::CommonMsgInfo::IntMsgInfo(x) => Some(x.created_lt), - ton_block::CommonMsgInfo::ExtOutMsgInfo(x) => Some(x.created_lt), - ton_block::CommonMsgInfo::ExtInMsgInfo(_) => None, + ton_block::CommonMsgInfo::IntMsgInfo(x) => x.created_lt, + ton_block::CommonMsgInfo::ExtOutMsgInfo(x) => x.created_lt, + ton_block::CommonMsgInfo::ExtInMsgInfo(_) => 0, } } @@ -1426,6 +1425,22 @@ impl Address { nt::utils::pack_std_smc_addr(url_safe, &self.0, bounce).handle_value_error() } + fn as_cell(&self) -> PyResult { + self.0.serialize().handle_value_error().map(Cell) + } + + fn as_slice(&self) -> PyResult { + self.as_cell()?.as_slice() + } + + fn as_builder(&self) -> PyResult { + let cell = self.as_cell()?; + Ok(CellBuilder { + builder: ton_types::BuilderData::from_cell(&cell.0), + is_exotic: false, + }) + } + fn __str__(&self) -> String { self.0.to_string() } @@ -1995,8 +2010,8 @@ impl CellBuilder { Ok(()) } - fn store_bit(&mut self, bit: bool) -> PyResult<()> { - self.builder.append_bit_bool(bit).handle_value_error()?; + fn store_bit(&mut self, value: bool) -> PyResult<()> { + self.builder.append_bit_bool(value).handle_value_error()?; Ok(()) } @@ -2104,8 +2119,8 @@ impl CellBuilder { Ok(()) } - fn store_public_key(&mut self, key: &PublicKey) -> PyResult<()> { - self.store_bytes(key.0.as_bytes()) + fn store_public_key(&mut self, value: &PublicKey) -> PyResult<()> { + self.store_bytes(value.0.as_bytes()) } fn store_signature(&mut self, signature: &Signature) -> PyResult<()> { diff --git a/src/transport.rs b/src/transport.rs index 6b129fc..998cb43 100644 --- a/src/transport.rs +++ b/src/transport.rs @@ -12,6 +12,7 @@ use tokio_util::sync::{CancellationToken, DropGuard}; use ton_block::Deserializable; use crate::abi::SignedExternalMessage; +use crate::crypto::SignatureContext; use crate::models::{AccountState, Address, BlockchainConfig, Message, Transaction}; use crate::util::*; @@ -155,6 +156,22 @@ impl Transport { }) } + pub fn get_signature_context<'a>(&self, py: Python<'a>) -> PyResult<&'a PyAny> { + let state = self.0.clone(); + pyo3_asyncio::tokio::future_into_py(py, async move { + let capabilities = state + .handle + .as_ref() + .get_capabilities(state.clock.as_ref()) + .await + .handle_runtime_error()?; + Ok(SignatureContext { + global_id: capabilities.global_id, + capabilities: capabilities.raw, + }) + }) + } + pub fn get_blockchain_config<'a>( &self, py: Python<'a>, diff --git a/tests/basic.py b/tests/basic.py index bbde909..cbbaa94 100644 --- a/tests/basic.py +++ b/tests/basic.py @@ -1,195 +1,217 @@ import asyncio -import os import logging +import os -from nekoton import * +import nekoton as nt -FORMAT = '%(levelname)s %(name)s %(asctime)-15s %(filename)s:%(lineno)d %(message)s' +FORMAT = "%(levelname)s %(name)s %(asctime)-15s %(filename)s:%(lineno)d %(message)s" logging.basicConfig(format=FORMAT) logging.getLogger().setLevel(logging.DEBUG) dirname = os.path.dirname(__file__) # Address -assert (not Address.validate("totally invalid address")) -assert (Address.validate( - "0:d84e969feb02481933382c4544e9ff24a2f359847f8896baa86c501c3d1b00cf")) -my_addr = Address( - '-1:d84e969feb02481933382c4544e9ff24a2f359847f8896baa86c501c3d1b00cf') -assert (my_addr.workchain == -1 and len(my_addr.account) == 32) +assert not nt.Address.validate("totally invalid address") +assert nt.Address.validate( + "0:d84e969feb02481933382c4544e9ff24a2f359847f8896baa86c501c3d1b00cf" +) +my_addr = nt.Address( + "-1:d84e969feb02481933382c4544e9ff24a2f359847f8896baa86c501c3d1b00cf" +) +assert my_addr.workchain == -1 and len(my_addr.account) == 32 -assert (my_addr.to_base64() == "Uf_YTpaf6wJIGTM4LEVE6f8kovNZhH-IlrqobFAcPRsAzwXV") +assert my_addr.to_base64() == "Uf_YTpaf6wJIGTM4LEVE6f8kovNZhH-IlrqobFAcPRsAzwXV" my_addr.workchain = 0 -assert (my_addr.workchain == 0) -assert (my_addr.__hash__() == my_addr.__hash__()) +assert my_addr.workchain == 0 +assert my_addr.__hash__() == my_addr.__hash__() address_dict = {my_addr: 123} -assert (address_dict[my_addr] == 123) +assert address_dict[my_addr] == 123 # CellBuilder -builder = CellBuilder() +builder = nt.CellBuilder() builder.store_zeros(10) builder.store_ones(6) -builder.store_reference(Cell()) +builder.store_reference(nt.Cell()) builder.store_u16(123) -builder.store_bytes(bytes.fromhex("d84e969feb02481933382c4544e9ff24a2f359847f8896baa86c501c3d1b00cf")) +builder.store_bytes( + bytes.fromhex("d84e969feb02481933382c4544e9ff24a2f359847f8896baa86c501c3d1b00cf") +) print(builder.build().encode()) # CellSlice cs = builder.build().as_slice() -assert(cs.load_u16() == 63) -assert(cs.load_u16() == 123) -assert(cs.load_reference() == Cell()) -assert(cs.load_bytes(32) == bytes.fromhex("d84e969feb02481933382c4544e9ff24a2f359847f8896baa86c501c3d1b00cf")) -assert(cs.is_empty()) +assert cs.load_u16() == 63 +assert cs.load_u16() == 123 +assert cs.load_reference() == nt.Cell() +assert cs.load_bytes(32) == bytes.fromhex( + "d84e969feb02481933382c4544e9ff24a2f359847f8896baa86c501c3d1b00cf" +) +assert cs.is_empty() # Cells -cell1 = Cell() -assert (len(Cell().repr_hash) == 32) -assert (Cell() == Cell()) +cell1 = nt.Cell() +assert len(nt.Cell().repr_hash) == 32 +assert nt.Cell() == nt.Cell() complex_cell_abi = [ - ("first", AbiUint(32)), - ("second", AbiBool()), + ("first", nt.AbiUint(32)), + ("second", nt.AbiBool()), ] -complex_cell = Cell.build( +complex_cell = nt.Cell.build( abi=complex_cell_abi, value={ "first": 123, "second": True, - } + }, ) -assert (Cell.decode(complex_cell.encode('base64'), 'base64') == complex_cell) -assert (Cell.from_bytes(complex_cell.to_bytes()) == complex_cell) +assert nt.Cell.decode(complex_cell.encode("base64"), "base64") == complex_cell +assert nt.Cell.from_bytes(complex_cell.to_bytes()) == complex_cell decoded = complex_cell.unpack(abi=complex_cell_abi) -assert (decoded["first"] == 123) -assert (decoded["second"] is True) +assert decoded["first"] == 123 +assert decoded["second"] is True # Crypto -some_pubkey = PublicKey( - '7b671b6bfd43e306d4accb46113a871e66b30cc587a57635766a2f360ee831c6') +some_pubkey = nt.PublicKey( + "7b671b6bfd43e306d4accb46113a871e66b30cc587a57635766a2f360ee831c6" +) print(some_pubkey) -assert some_pubkey == PublicKey.from_int( - 55816654881951532897500201008042388765613920635159317416907795053873153454534) -assert (some_pubkey.to_int() == - 55816654881951532897500201008042388765613920635159317416907795053873153454534) - -assert PublicKey('00005a641f7deda1442badd9ed761dd4e948580c7d7f36b3f858ab26b1af6fa1') == PublicKey.from_int( - 623856482362781547816624847020167340480982121355398289209492515375247265) -assert (PublicKey('00005a641f7deda1442badd9ed761dd4e948580c7d7f36b3f858ab26b1af6fa1').to_int() - == 623856482362781547816624847020167340480982121355398289209492515375247265) - -cell_with_pubkey_abi = [ - ("pubkey", AbiUint(256)), - ("not_pubkey", AbiUint(256)), +assert some_pubkey == nt.PublicKey.from_int( + 55816654881951532897500201008042388765613920635159317416907795053873153454534 +) +assert ( + some_pubkey.to_int() + == 55816654881951532897500201008042388765613920635159317416907795053873153454534 +) + +assert nt.PublicKey( + "00005a641f7deda1442badd9ed761dd4e948580c7d7f36b3f858ab26b1af6fa1" +) == nt.PublicKey.from_int( + 623856482362781547816624847020167340480982121355398289209492515375247265 +) +assert ( + nt.PublicKey( + "00005a641f7deda1442badd9ed761dd4e948580c7d7f36b3f858ab26b1af6fa1" + ).to_int() + == 623856482362781547816624847020167340480982121355398289209492515375247265 +) + +cell_with_pubkey_abi: list[tuple[str, nt.AbiParam]] = [ + ("pubkey", nt.AbiUint(256)), + ("not_pubkey", nt.AbiUint(256)), ] -cell_with_pubkey = Cell.build( +cell_with_pubkey = nt.Cell.build( abi=cell_with_pubkey_abi, value={ "pubkey": some_pubkey, "not_pubkey": 123123, - } + }, ) decoded_cell_with_pubkey = cell_with_pubkey.unpack(cell_with_pubkey_abi) -assert PublicKey.from_int(decoded_cell_with_pubkey['pubkey']) == some_pubkey -assert decoded_cell_with_pubkey['not_pubkey'] == 123123 +assert nt.PublicKey.from_int(decoded_cell_with_pubkey["pubkey"]) == some_pubkey +assert decoded_cell_with_pubkey["not_pubkey"] == 123123 -seed = Bip39Seed.generate() -assert (seed.word_count == 12) +seed = nt.Bip39Seed.generate() +assert seed.word_count == 12 print(seed) keypair0 = seed.derive() -assert (len(keypair0.public_key.to_bytes()) == 32) -assert (seed.derive() == seed.derive(path=Bip39Seed.path_for_account(0))) +assert len(keypair0.public_key.to_bytes()) == 32 +assert seed.derive() == seed.derive(path=nt.Bip39Seed.path_for_account(0)) -keypair1 = seed.derive(path=Bip39Seed.path_for_account(1)) -assert (keypair0 != keypair1) -assert (len(keypair1.public_key.encode('hex')) == 64) +keypair1 = seed.derive(path=nt.Bip39Seed.path_for_account(1)) +assert keypair0 != keypair1 +assert len(keypair1.public_key.encode("hex")) == 64 # Abi -with open(os.path.join(dirname, 'wallet.abi.json'), 'r') as json: - abi = ContractAbi(json.read()) +with open(os.path.join(dirname, "wallet.abi.json"), "r") as json: + abi = nt.ContractAbi(json.read()) -assert (abi.abi_version == AbiVersion(2, 3)) -assert (abi.get_function("non-existing") is None) -assert (abi.get_event("non-existing") is None) +assert abi.abi_version == nt.AbiVersion(2, 3) +assert abi.get_function("non-existing") is None +assert abi.get_event("non-existing") is None -send_transaction_func = abi.get_function("sendTransaction") -assert (send_transaction_func is not None) +send_transaction_func = abi.function("sendTransaction") +assert send_transaction_func is not None send_transaction_input = { "dest": my_addr, - "value": Tokens('1.5'), + "value": nt.Tokens("1.5"), "bounce": False, "flags": 3, - "payload": Cell(), + "payload": nt.Cell(), } body_cell = send_transaction_func.encode_internal_input(send_transaction_input) print(body_cell) -assert (body_cell != Cell()) +assert body_cell != nt.Cell() internal_msg = send_transaction_func.encode_internal_message( input=send_transaction_input, - value=Tokens('1.5'), + value=nt.Tokens("1.5"), bounce=False, dst=my_addr, ) print(internal_msg) -assert (internal_msg.state_init is None) -assert (internal_msg.body == body_cell) -assert (isinstance(internal_msg.header, InternalMessageHeader)) - -unpacked_body = body_cell.unpack(abi=[ - ("function_id", AbiUint(32)), - ("dest", AbiAddress()), - ("value", AbiUint(128)), - ("bounce", AbiBool()), - ("flags", AbiUint(8)), - ("payload", AbiCell()), -]) -decoded_body = send_transaction_func.decode_input( - message_body=body_cell, internal=True) +assert internal_msg.state_init is None +assert internal_msg.body == body_cell +assert isinstance(internal_msg.header, nt.InternalMessageHeader) + +unpacked_body = body_cell.unpack( + abi=[ + ("function_id", nt.AbiUint(32)), + ("dest", nt.AbiAddress()), + ("value", nt.AbiUint(128)), + ("bounce", nt.AbiBool()), + ("flags", nt.AbiUint(8)), + ("payload", nt.AbiCell()), + ] +) +decoded_body = send_transaction_func.decode_input(message_body=body_cell, internal=True) for field, item in send_transaction_input.items(): - if isinstance(item, Tokens): - assert (Tokens.from_nano(unpacked_body[field]) == item) - assert (Tokens.from_nano(decoded_body[field]) == item) + if isinstance(item, nt.Tokens): + assert nt.Tokens.from_nano(unpacked_body[field]) == item + assert nt.Tokens.from_nano(decoded_body[field]) == item else: - assert (unpacked_body[field] == item) - assert (decoded_body[field] == item) + assert unpacked_body[field] == item + assert decoded_body[field] == item unsigned_body = send_transaction_func.encode_external_input( - send_transaction_input, public_key=None, address=my_addr) -assert (unsigned_body.sign(keypair0, signature_id=None) == unsigned_body.with_signature( - keypair0.sign_raw(unsigned_body.hash, signature_id=None))) + send_transaction_input, public_key=None, address=my_addr +) +assert unsigned_body.sign(keypair0, context=None) == unsigned_body.with_signature( + keypair0.sign_raw(unsigned_body.hash, context=None) +) unsigned_message = send_transaction_func.encode_external_message( - my_addr, send_transaction_input, public_key=None) + my_addr, send_transaction_input, public_key=None +) print(unsigned_message.without_signature()) external_msg, _ = unsigned_message.without_signature().split() -assert (len(external_msg.hash) == 32) -assert (isinstance(external_msg.header, ExternalInMessageHeader)) +assert len(external_msg.hash) == 32 +assert isinstance(external_msg.header, nt.ExternalInMessageHeader) -depool_abi = ContractAbi.from_file(os.path.join(dirname, 'depool.abi.json')) +depool_abi = nt.ContractAbi.from_file(os.path.join(dirname, "depool.abi.json")) -tokens = Tokens('10.123456789') +tokens = nt.Tokens("10.123456789") assert (2 * tokens / 2) == tokens -token_wallet_abi = ContractAbi.from_file( - os.path.join(dirname, 'token_wallet.abi.json')) +token_wallet_abi = nt.ContractAbi.from_file( + os.path.join(dirname, "token_wallet.abi.json") +) -base64_string = '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' -tree = TransactionTree.decode(base64_string) +base64_string = "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" +tree = nt.TransactionTree.decode(base64_string) assert tree.root is not None assert tree.root.out_msgs_len == len(tree.children) for tx in tree: assert tx is not None # Asm -code = Asm.compile(""" +code = nt.Asm.compile(""" SETCP 0 DICTPUSHCONST 19, [ 0 => { @@ -204,89 +226,106 @@ DICTIGETJMPZ THROWARG 11 """) -assert code == Cell.decode("te6ccgEBBAEAHgABFP8A9KQT9LzyyAsBAgLOAwIABaNUQAAJ0IPAWpI=") +assert code == nt.Cell.decode( + "te6ccgEBBAEAHgABFP8A9KQT9LzyyAsBAgLOAwIABaNUQAAJ0IPAWpI=" +) + # Subscriptions async def main(): - clock = Clock() + _clock = nt.Clock() - transport = JrpcTransport(endpoint="https://jrpc.everwallet.net") + transport = nt.JrpcTransport(endpoint="https://jrpc.everwallet.net") await transport.check_connection() signature_id = await transport.get_signature_id() - assert (signature_id is None) + assert signature_id is None config = await transport.get_blockchain_config() - assert (config.contains_param(0)) - assert (config.config_address == Address( - "-1:5555555555555555555555555555555555555555555555555555555555555555")) - assert (config.elector_address == Address( - "-1:3333333333333333333333333333333333333333333333333333333333333333")) + assert config.contains_param(0) + assert config.config_address == nt.Address( + "-1:5555555555555555555555555555555555555555555555555555555555555555" + ) + assert config.elector_address == nt.Address( + "-1:3333333333333333333333333333333333333333333333333333333333333333" + ) account = await transport.get_account_state(my_addr) print(account) - assert (account is not None) - assert (account.status == AccountStatus.Active) - assert (not account.balance.is_zero) - assert (account.state_init.code is not None) - - token_wallet_addr = Address( - "0:c7c7abe480e6ad15631e4353ca5dd395ee91b56c50e71cfc67780b928b138974") + assert account is not None + assert account.status == nt.AccountStatus.Active + assert not account.balance.is_zero + assert account.state_init is not None + assert account.state_init.code is not None + + token_wallet_addr = nt.Address( + "0:c7c7abe480e6ad15631e4353ca5dd395ee91b56c50e71cfc67780b928b138974" + ) token_wallet_account = await transport.get_account_state(token_wallet_addr) - assert (token_wallet_account is not None) + assert token_wallet_account is not None fields = token_wallet_abi.decode_fields(token_wallet_account) - assert (fields['root_'] == Address( - '0:a519f99bb5d6d51ef958ed24d337ad75a1c770885dcd42d51d6663f9fcdacfb2')) - - executor = TransactionExecutor(config, check_signature=False) - tx, new_state = executor.execute( - unsigned_message.with_fake_signature(), account) - assert (not tx.aborted) - assert (tx.compute_phase.exit_code == 0) - assert (new_state is not None) - - depool_addr = Address( - "0:c9b7e458134c655123878fe7980c7118adb314fbbec32e3b7c155fea90f87a97") + assert fields["root_"] == nt.Address( + "0:a519f99bb5d6d51ef958ed24d337ad75a1c770885dcd42d51d6663f9fcdacfb2" + ) + + executor = nt.TransactionExecutor(config, check_signature=False) + tx, new_state = executor.execute(unsigned_message.with_fake_signature(), account) + assert not tx.aborted + assert tx.compute_phase is not None + assert tx.compute_phase.exit_code == 0 + assert new_state is not None + + depool_addr = nt.Address( + "0:c9b7e458134c655123878fe7980c7118adb314fbbec32e3b7c155fea90f87a97" + ) depool_state = await transport.get_account_state(depool_addr) + assert depool_state is not None - depool_info = depool_abi.get_function( - "getDePoolInfo").call(depool_state, input={}) - assert (depool_info.exit_code == 0) - assert (depool_info.output is not None) + depool_info = depool_abi.function("getDePoolInfo").call(depool_state, input={}) + assert depool_info.exit_code == 0 + assert depool_info.output is not None stake_accept_tx = await transport.get_transaction( - bytes.fromhex("60a311aa3e3c1f30deb3010bb09d0079713ab1b0af07f5fd2ca87f5b282912a4")) + bytes.fromhex( + "60a311aa3e3c1f30deb3010bb09d0079713ab1b0af07f5fd2ca87f5b282912a4" + ) + ) + assert stake_accept_tx is not None + print(stake_accept_tx) - on_stake_accept_func = depool_abi.get_function("onStakeAccept") - parsed_stake_accept = on_stake_accept_func.decode_transaction( - stake_accept_tx) - assert (parsed_stake_accept.input['queryId'] == 93) - assert (parsed_stake_accept.output == {}) + on_stake_accept_func = depool_abi.function("onStakeAccept") + parsed_stake_accept = on_stake_accept_func.decode_transaction(stake_accept_tx) + assert parsed_stake_accept.input["queryId"] == 93 + assert parsed_stake_accept.output == {} await transport.trace_transaction(stake_accept_tx).wait() full_parsed_stake_accept = depool_abi.decode_transaction(stake_accept_tx) - assert (full_parsed_stake_accept.function == on_stake_accept_func) - assert (len(full_parsed_stake_accept.events) == 1) - assert (full_parsed_stake_accept.events[0][0] == depool_abi.get_event( - "RoundStakeIsAccepted")) + assert full_parsed_stake_accept is not None + assert full_parsed_stake_accept.function == on_stake_accept_func + assert len(full_parsed_stake_accept.events) == 1 + assert full_parsed_stake_accept.events[0][0] == depool_abi.event( + "RoundStakeIsAccepted" + ) code_hash = bytes.fromhex( - "7d0996943406f7d62a4ff291b1228bf06ebd3e048b58436c5b70fb77ff8b4bf2") + "7d0996943406f7d62a4ff291b1228bf06ebd3e048b58436c5b70fb77ff8b4bf2" + ) addresses = await transport.get_accounts_by_code_hash(code_hash, limit=10) - assert (len(addresses) == 10) + assert len(addresses) == 10 transactions = await transport.get_transactions(my_addr, limit=10) - assert (len(transactions) == 10) + assert len(transactions) == 10 for tx in transactions: - assert (len(tx.hash) == 32) + assert len(tx.hash) == 32 assert tx.has_in_msg - assert (tx.type == TransactionType.Ordinary) + assert tx.type == nt.TransactionType.Ordinary latest_tx = transactions[0] fetched_tx = await transport.get_transaction(latest_tx.hash) - assert (latest_tx == fetched_tx) + assert latest_tx == fetched_tx + assert latest_tx.in_msg_hash is not None fetched_next_tx = await transport.get_dst_transaction(latest_tx.in_msg_hash) - assert (fetched_next_tx == fetched_tx) + assert fetched_next_tx == fetched_tx async with transport.account_states(my_addr) as states: # There is always at least one iteration with the current state @@ -294,7 +333,7 @@ async def main(): if state is not None: print(my_addr, state.balance) else: - print(my_addr, 'account not exists') + print(my_addr, "account not exists") break # for tests @@ -305,7 +344,8 @@ async def main(): break deep_tx_hash = bytes.fromhex( - 'a82e6603dec13499dd43486203b3304122ae89c13a80b189eef8976834cba413') + "a82e6603dec13499dd43486203b3304122ae89c13a80b189eef8976834cba413" + ) deep_tx_nodes = 0 async for transaction in transport.trace_transaction(deep_tx_hash, yield_root=True): deep_tx_nodes += 1 diff --git a/tests/gql_queries.py b/tests/gql_queries.py index 0bb89cd..128c616 100644 --- a/tests/gql_queries.py +++ b/tests/gql_queries.py @@ -1,42 +1,61 @@ import asyncio import logging -from nekoton import * -FORMAT = '%(levelname)s %(name)s %(asctime)-15s %(filename)s:%(lineno)d %(message)s' +import nekoton as nt +from nekoton import gql + +FORMAT = "%(levelname)s %(name)s %(asctime)-15s %(filename)s:%(lineno)d %(message)s" logging.basicConfig(format=FORMAT) logging.getLogger().setLevel(logging.DEBUG) async def main(): - transport = GqlTransport( - endpoints=["mainnet.evercloud.dev/89a3b8f46a484f2ea3bdd364ddaee3a3"]) + transport = nt.GqlTransport( + endpoints=["mainnet.evercloud.dev/89a3b8f46a484f2ea3bdd364ddaee3a3"] + ) await transport.check_connection() - strange_addr = Address( - "-1:04f64c6afbff3dd10d8ba6707790ac9670d540f37a9448b0337baa6a5a92acac") - - transactions = await transport.query_transactions([ - gql.tx.AccountAddr() == strange_addr, - gql.tx.TrType() == TransactionType.Tock - ], order_by=[ - gql.tx.Lt().desc(), - ]) + strange_addr = nt.Address( + "-1:04f64c6afbff3dd10d8ba6707790ac9670d540f37a9448b0337baa6a5a92acac" + ) + + transactions = await transport.query_transactions( + [ + gql.tx.AccountAddr() == strange_addr, + gql.tx.TrType() == nt.TransactionType.Tock, + ], + order_by=[ + gql.tx.Lt().desc(), + ], + ) print(transactions) - messages = await transport.query_messages(gql.or_([ - gql.msg.Src() == strange_addr, - gql.msg.Dst() == strange_addr, - ]), order_by=[ - gql.msg.CreatedLt().desc(), - ]) + messages = await transport.query_messages( + gql.or_( + [ + gql.msg.Src() == strange_addr, + gql.msg.Dst() == strange_addr, + ] + ), + order_by=[ + gql.msg.CreatedLt().desc(), + ], + ) print(messages) - accounts = await transport.query_accounts(gql.or_([ - gql.acc.Id() == Address("-1:3333333333333333333333333333333333333333333333333333333333333333"), - gql.acc.CodeHash() == "80d6c47c4a25543c9b397b71716f3fae1e2c5d247174c52e2c19bd896442b105" - ]), order_by=[ - gql.acc.LastTransLt().asc() - ]) + accounts = await transport.query_accounts( + gql.or_( + [ + gql.acc.Id() + == nt.Address( + "-1:3333333333333333333333333333333333333333333333333333333333333333" + ), + gql.acc.CodeHash() + == "80d6c47c4a25543c9b397b71716f3fae1e2c5d247174c52e2c19bd896442b105", + ] + ), + order_by=[gql.acc.LastTransLt().asc()], + ) for addr, state in accounts: print(addr, state) diff --git a/tests/highload_wallet.py b/tests/highload_wallet.py index 7a4cd1a..33efe8b 100644 --- a/tests/highload_wallet.py +++ b/tests/highload_wallet.py @@ -1,18 +1,17 @@ import asyncio -from nekoton import * + +import nekoton as nt # Subscriptions async def main(): - clock = Clock() - - transport = JrpcTransport(endpoint="https://jrpc.everwallet.net") + transport = nt.JrpcTransport(endpoint="https://jrpc.everwallet.net") await transport.check_connection() signature_id = await transport.get_signature_id() assert signature_id is None - keypair = KeyPair.generate() - wallet = contracts.HighloadWalletV2(transport, keypair) + keypair = nt.KeyPair.generate() + wallet = nt.contracts.HighloadWalletV2(transport, keypair) print(wallet.address) async with transport.account_states(wallet.address) as states: @@ -23,11 +22,11 @@ async def main(): continue print("account balance: ", state.balance) - if state.balance >= Tokens(1): + if state.balance >= nt.Tokens(1): break print("Balance is enough") - tx = await wallet.send(dst=wallet.address, value=Tokens("0.5")) + tx = await wallet.send(dst=wallet.address, value=nt.Tokens("0.5")) await transport.trace_transaction(tx).wait() print("Done") diff --git a/tests/timeouts.py b/tests/timeouts.py index 0c718b8..68b54f2 100644 --- a/tests/timeouts.py +++ b/tests/timeouts.py @@ -1,7 +1,7 @@ import asyncio import logging -from nekoton import * +import nekoton as nt FORMAT = "%(levelname)s %(name)s %(asctime)-15s %(filename)s:%(lineno)d %(message)s" logging.basicConfig(format=FORMAT) @@ -9,15 +9,15 @@ async def main(): - clock = Clock() - transport = ProtoTransport(endpoint="https://jrpc.everwallet.net", clock=clock) + clock = nt.Clock() + transport = nt.ProtoTransport(endpoint="https://jrpc.everwallet.net", clock=clock) await transport.check_connection() - address = Address( + address = nt.Address( "0:0000000000000000000000000000000000000000000000000000000000000000" ) - external_message = SignedExternalMessage( + external_message = nt.SignedExternalMessage( address, clock.now_sec + 60, body=None, @@ -25,6 +25,7 @@ async def main(): ) tx = await transport.send_external_message(external_message) print(tx) + assert tx is not None await transport.trace_transaction(tx).wait()