From ad00c76890c94997041d6af49586c19e7543248f Mon Sep 17 00:00:00 2001 From: Arjan Zijderveld <5286904+arjanz@users.noreply.github.com> Date: Thu, 28 Sep 2023 15:30:46 +0200 Subject: [PATCH] Added runtime interfaces --- examples/extensions.py | 16 +- substrateinterface/base.py | 168 +--------- substrateinterface/interfaces.py | 530 ++++++++++++++++++++++++++++++- substrateinterface/storage.py | 2 +- test/fixtures/flipper.contract | 1 + test/test_create_extrinsics.py | 39 +-- test/test_runtime_call.py | 12 +- test/test_runtime_interface.py | 161 ++++++++++ 8 files changed, 739 insertions(+), 190 deletions(-) create mode 100644 test/fixtures/flipper.contract create mode 100644 test/test_runtime_interface.py diff --git a/examples/extensions.py b/examples/extensions.py index 8a7f83d..4142ea8 100644 --- a/examples/extensions.py +++ b/examples/extensions.py @@ -11,12 +11,22 @@ substrate.register_extension(SubstrateNodeExtension(max_block_range=100)) # Search for block number corresponding a specific datetime -block_datetime = datetime(2022, 1, 1, 0, 0, 0) +block_datetime = datetime(2022, 12, 31, 0, 0, 0) block_number = substrate.extensions.search_block_number(block_datetime=block_datetime) print(f'Block number for {block_datetime}: #{block_number}') +block_hash = substrate.get_block_hash(block_number) -# account_info = substrate.runtime. -# exit() +account_info = substrate.runtime.at(block_hash).pallet("System").storage("Account").get("13GnsRKEXCAYLJNScBEDj7rHTXkkHAVTj5QMNp6rnyGuTAVN") + +def format_balance(amount: int): + amount = format(amount / 10**substrate.properties.get('tokenDecimals', 0), ".15g") + return f"{amount} {substrate.properties.get('tokenSymbol', 'UNIT')}" + +balance = (account_info.value["data"]["free"] + account_info.value["data"]["reserved"]) + +print(f"Balance @ {block_number}: {format_balance(balance)}") + +exit() # Returns all `Balances.Transfer` events from the last 30 blocks events = substrate.extensions.filter_events(pallet_name="Balances", event_name="Transfer", block_start=-30) diff --git a/substrateinterface/base.py b/substrateinterface/base.py index 7374968..40a8bde 100644 --- a/substrateinterface/base.py +++ b/substrateinterface/base.py @@ -33,16 +33,16 @@ from scalecodec.type_registry import load_type_registry_preset from scalecodec.updater import update_type_registries from .extensions import Extension -from .interfaces import ExtensionInterface +from .interfaces import ExtensionInterface, RuntimeInterface, QueryMapResult, ChainInterface, BlockInterface, \ + ContractInterface from .storage import StorageKey from .exceptions import SubstrateRequestException, ConfigurationError, StorageFunctionNotFound, BlockNotFound, \ ExtrinsicNotFound, ExtensionCallNotFound from .constants import * -from .keypair import Keypair, KeypairType, MnemonicLanguageCode -from .utils.ss58 import ss58_decode, ss58_encode, is_valid_ss58_address, get_ss58_format - +from .keypair import Keypair +from .utils.ss58 import ss58_decode, ss58_encode, is_valid_ss58_address __all__ = ['SubstrateInterface', 'ExtrinsicReceipt', 'logger'] @@ -157,8 +157,11 @@ def __init__(self, url=None, websocket=None, ss58_format=None, type_registry=Non 'rpc_methods': None } - # Initialize extension interface + # Initialize interfaces + self.runtime = RuntimeInterface(self) + self.block = BlockInterface(self) self.extensions = ExtensionInterface(self) + self.contract = ContractInterface(self) self.session = requests.Session() @@ -756,7 +759,7 @@ def query_map(self, module: str, storage_function: str, params: Optional[list] = Returns ------- - QueryMapResult + substrateinterface.interfaces.QueryMapResult """ if block_hash is None: @@ -976,88 +979,16 @@ def subscription_handler(obj, update_nr, subscription_id): # Check requirements if callable(subscription_handler): raise ValueError("Subscriptions can only be registered for current state; block_hash cannot be set") - else: - # Retrieve chain tip - block_hash = self.get_chain_head() - - if params is None: - params = [] - - self.init_runtime(block_hash=block_hash) - - if module == 'Substrate': - # Search for 'well-known' storage keys - return self.__query_well_known(storage_function, block_hash) - - # Search storage call in metadata - metadata_pallet = self.metadata.get_metadata_pallet(module) - - if not metadata_pallet: - raise StorageFunctionNotFound(f'Pallet "{module}" not found') - - storage_item = metadata_pallet.get_storage_function(storage_function) - - if not metadata_pallet or not storage_item: - raise StorageFunctionNotFound(f'Storage function "{module}.{storage_function}" not found') - - # SCALE type string of value - param_types = storage_item.get_params_type_string() - value_scale_type = storage_item.get_value_type_string() - - if len(params) != len(param_types): - raise ValueError(f'Storage function requires {len(param_types)} parameters, {len(params)} given') - - if raw_storage_key: - storage_key = StorageKey.create_from_data( - data=raw_storage_key, pallet=module, storage_function=storage_function, - value_scale_type=value_scale_type, metadata=self.metadata, runtime_config=self.runtime_config - ) - else: - - storage_key = StorageKey.create_from_storage_function( - module, storage_function, params, runtime_config=self.runtime_config, metadata=self.metadata - ) if callable(subscription_handler): + raise NotImplementedError() - # Wrap subscription handler to discard storage key arg - def result_handler(storage_key, updated_obj, update_nr, subscription_id): - return subscription_handler(updated_obj, update_nr, subscription_id) - - return self.subscribe_storage([storage_key], result_handler) - - else: - - if self.supports_rpc_method('state_getStorageAt'): - response = self.rpc_request("state_getStorageAt", [storage_key.to_hex(), block_hash]) - else: - response = self.rpc_request("state_getStorage", [storage_key.to_hex(), block_hash]) - - if 'error' in response: - raise SubstrateRequestException(response['error']['message']) - - if 'result' in response: - if value_scale_type: - - if response.get('result') is not None: - query_value = response.get('result') - elif storage_item.value['modifier'] == 'Default': - # Fallback to default value of storage function if no result - query_value = storage_item.value_object['default'].value_object - else: - # No result is interpreted as an Option<...> result - value_scale_type = f'Option<{value_scale_type}>' - query_value = storage_item.value_object['default'].value_object - - obj = self.runtime_config.create_scale_object( - type_string=value_scale_type, - data=ScaleBytes(query_value), - metadata=self.metadata - ) - obj.decode() - obj.meta_info = {'result_found': response.get('result') is not None} + if params is None: + params = [] - return obj + return self.runtime.at(block_hash=block_hash).pallet(module).storage(storage_function).get( + *params, raw_storage_key=raw_storage_key + ) def __query_well_known(self, name: str, block_hash: str) -> ScaleType: """ @@ -1387,20 +1318,10 @@ def compose_call(self, call_module: str, call_function: str, call_params: dict = if call_params is None: call_params = {} - self.init_runtime(block_hash=block_hash) - - call = self.runtime_config.create_scale_object( - type_string='Call', metadata=self.metadata + return self.runtime.at(block_hash=block_hash).pallet(call_module).call(call_function).create( + **call_params ) - call.encode({ - 'call_module': call_module, - 'call_function': call_function, - 'call_args': call_params - }) - - return call - def get_account_nonce(self, account_address) -> int: """ Returns current nonce for given account address @@ -3399,58 +3320,3 @@ def get(self, name): return self[name] -class QueryMapResult: - - def __init__(self, records: list, page_size: int, module: str = None, storage_function: str = None, - params: list = None, block_hash: str = None, substrate: SubstrateInterface = None, - last_key: str = None, max_results: int = None, ignore_decoding_errors: bool = False): - self.current_index = -1 - self.records = records - self.page_size = page_size - self.module = module - self.storage_function = storage_function - self.block_hash = block_hash - self.substrate = substrate - self.last_key = last_key - self.max_results = max_results - self.params = params - self.ignore_decoding_errors = ignore_decoding_errors - self.loading_complete = False - - def retrieve_next_page(self, start_key) -> list: - if not self.substrate: - return [] - - result = self.substrate.query_map(module=self.module, storage_function=self.storage_function, - params=self.params, page_size=self.page_size, block_hash=self.block_hash, - start_key=start_key, max_results=self.max_results, - ignore_decoding_errors=self.ignore_decoding_errors) - - # Update last key from new result set to use as offset for next page - self.last_key = result.last_key - - return result.records - - def __iter__(self): - self.current_index = -1 - return self - - def __next__(self): - self.current_index += 1 - - if self.max_results is not None and self.current_index >= self.max_results: - self.loading_complete = True - raise StopIteration - - if self.current_index >= len(self.records) and not self.loading_complete: - # try to retrieve next page from node - self.records += self.retrieve_next_page(start_key=self.last_key) - - if self.current_index >= len(self.records): - self.loading_complete = True - raise StopIteration - - return self.records[self.current_index] - - def __getitem__(self, item): - return self.records[item] diff --git a/substrateinterface/interfaces.py b/substrateinterface/interfaces.py index 191facd..159e699 100644 --- a/substrateinterface/interfaces.py +++ b/substrateinterface/interfaces.py @@ -14,12 +14,430 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Callable +from typing import Callable, List, TYPE_CHECKING +from scalecodec import ScaleType, ScaleBytes +# from .contracts import ContractMetadata + +from .keypair import Keypair from .extensions import Extension -from .exceptions import ExtensionCallNotFound +from .exceptions import ExtensionCallNotFound, StorageFunctionNotFound, SubstrateRequestException + +__all__ = ['ExtensionInterface', 'RuntimeInterface'] + +from .storage import StorageKey + +if TYPE_CHECKING: + from .base import SubstrateInterface + + +class StorageFunctionInterface: + + def __init__(self, pallet_interface: 'RuntimePalletInterface', name: str): + self.pallet_interface = pallet_interface + self.name = name + + def get_metadata_obj(self): + pallet = self.pallet_interface.get_metadata_obj() + + if not pallet: + raise StorageFunctionNotFound(f'Pallet "{self.pallet_interface.name}" not found') + + storage = pallet.get_storage_function(self.name) + if not storage: + raise StorageFunctionNotFound(f'Storage function "{self.pallet_interface.name}.{self.name}" not found') + + return storage + + def get(self, *args, raw_storage_key=None): + + self.pallet_interface.runtime_interface.init() + + block_hash = self.pallet_interface.runtime_interface.block_hash + substrate = self.pallet_interface.runtime_interface.substrate + + # SCALE type string of value + storage_function = self.get_metadata_obj() + param_types = storage_function.get_params_type_string() + value_scale_type = storage_function.get_value_type_string() + + if len(args) != len(param_types): + raise ValueError(f'Storage function requires {len(param_types)} parameters, {len(args)} given') + + if raw_storage_key: + storage_key = StorageKey.create_from_data( + data=raw_storage_key, pallet=self.pallet_interface.name, + storage_function=self.name, value_scale_type=value_scale_type, + metadata=substrate.metadata, + runtime_config=substrate.runtime_config + ) + else: + storage_key = StorageKey.create_from_storage_function( + self.pallet_interface.name, self.name, list(args), + metadata=substrate.metadata, + runtime_config=substrate.runtime_config + ) + + # RPC call node + if substrate.supports_rpc_method('state_getStorageAt'): + response = substrate.rpc_request("state_getStorageAt", [storage_key.to_hex(), block_hash]) + else: + response = substrate.rpc_request("state_getStorage", [storage_key.to_hex(), block_hash]) + + if 'error' in response: + raise SubstrateRequestException(response['error']['message']) + + if 'result' in response: + if value_scale_type: + + if response.get('result') is not None: + query_value = response.get('result') + elif storage_function.value['modifier'] == 'Default': + # Fallback to default value of storage function if no result + query_value = storage_function.value_object['default'].value_object + else: + # No result is interpreted as an Option<...> result + value_scale_type = f'Option<{value_scale_type}>' + query_value = storage_function.value_object['default'].value_object + + obj = substrate.runtime_config.create_scale_object( + type_string=value_scale_type, + data=ScaleBytes(query_value), + metadata=substrate.metadata + ) + obj.decode() + obj.meta_info = {'result_found': response.get('result') is not None} + + return obj + + def list(self, *args, max_results: int = None, start_key: str = None, page_size: int = 100, + ignore_decoding_errors: bool = True) -> 'QueryMapResult': + + self.pallet_interface.runtime_interface.init() + + block_hash = self.pallet_interface.runtime_interface.block_hash + substrate = self.pallet_interface.runtime_interface.substrate + + # SCALE type string of value + storage_item = self.get_metadata_obj() + + value_type = storage_item.get_value_type_string() + param_types = storage_item.get_params_type_string() + key_hashers = storage_item.get_param_hashers() + + # Check MapType condititions + if len(param_types) == 0: + raise ValueError('Given storage function is not a map') + + if len(args) != len(param_types) - 1: + raise ValueError(f'Storage function map requires {len(param_types) - 1} parameters, {len(args)} given') + + # Generate storage key prefix + storage_key = StorageKey.create_from_storage_function( + pallet=self.pallet_interface.name, storage_function=self.name, params=list(args), + metadata=substrate.metadata, runtime_config=substrate.runtime_config + ) + prefix = storage_key.to_hex() + + if not start_key: + start_key = prefix + + # Make sure if the max result is smaller than the page size, adjust the page size + if max_results is not None and max_results < page_size: + page_size = max_results + + # Retrieve storage keys + response = substrate.rpc_request(method="state_getKeysPaged", params=[prefix, page_size, start_key, block_hash]) + + if 'error' in response: + raise SubstrateRequestException(response['error']['message']) + + result_keys = response.get('result') + + result = [] + last_key = None + + def concat_hash_len(key_hasher: str) -> int: + if key_hasher == "Blake2_128Concat": + return 32 + elif key_hasher == "Twox64Concat": + return 16 + elif key_hasher == "Identity": + return 0 + else: + raise ValueError('Unsupported hash type') + + if len(result_keys) > 0: + + last_key = result_keys[-1] + + # Retrieve corresponding value + response = substrate.rpc_request(method="state_queryStorageAt", params=[result_keys, block_hash]) + + if 'error' in response: + raise SubstrateRequestException(response['error']['message']) + + for result_group in response['result']: + for item in result_group['changes']: + try: + item_key = substrate.decode_scale( + type_string=param_types[len(args)], + scale_bytes='0x' + item[0][len(prefix) + concat_hash_len(key_hashers[len(params)]):], + return_scale_obj=True, + block_hash=block_hash + ) + except Exception: + if not ignore_decoding_errors: + raise + item_key = None + + try: + item_value = substrate.decode_scale( + type_string=value_type, + scale_bytes=item[1], + return_scale_obj=True, + block_hash=block_hash + ) + except Exception: + if not ignore_decoding_errors: + raise + item_value = None + + result.append([item_key, item_value]) + + return QueryMapResult( + records=result, page_size=page_size, module=self.pallet_interface.name, storage_function=self.name, + params=list(args), block_hash=block_hash, substrate=substrate, last_key=last_key, max_results=max_results, + ignore_decoding_errors=ignore_decoding_errors + ) + + def multi(self, params_list: list) -> list: + pass + + def subscribe(self, **kwargs): + pass + + +class RuntimeAPIInterface: + + def __init__(self, runtime_interface, name: str, params: dict = None): + self.runtime_interface = runtime_interface + self.name = name + self.params = params + + +class RuntimeCallInterface: + + def __init__(self, pallet_interface: 'RuntimePalletInterface', name: str): + self.pallet_interface = pallet_interface + self.name = name + self.call = None + + def create(self, **kwargs): + self.pallet_interface.runtime_interface.init() + + substrate = self.pallet_interface.runtime_interface.substrate + + call = substrate.runtime_config.create_scale_object( + type_string='Call', metadata=substrate.metadata + ) + + call.encode({ + 'call_module': self.pallet_interface.name, + 'call_function': self.name, + 'call_args': kwargs + }) + + return call + + def sign_and_submit(self, call, keypair: Keypair, era: dict = None, nonce: int = None, tip: int = 0, + tip_asset_id: int = None, wait_for_inclusion: bool = False, wait_for_finalization: bool = False + ) -> "ExtrinsicReceipt": + + substrate = self.pallet_interface.runtime_interface.substrate + + extrinsic = substrate.create_signed_extrinsic( + call=call, keypair=keypair, era=era, nonce=nonce, tip=tip, tip_asset_id=tip_asset_id + ) + return substrate.submit_extrinsic( + extrinsic, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization + ) + + def get_param_info(self): + pass + + def metadata(self): + self.pallet_interface.runtime_interface.init() + + pallet = self.pallet_interface.get_metadata_obj() + + if not pallet: + raise ValueError(f'Pallet "{self.pallet_interface.name}" not found') + + for call in pallet.calls: + if call.name == self.name: + return call + + raise ValueError(f'Storage function "{self.pallet_interface.name}.{self.name}" not found') + + +class ConstantInterface: + def __init__(self, runtime_interface): + self.runtime_interface = runtime_interface + + def get(self, **kwargs): + pass + + def info(self): + pass + + +class StorageInterface: + + def __init__(self, runtime_interface: 'RuntimeInterface'): + self.runtime_interface = runtime_interface + + def multi(self, storage_keys: List[StorageKey]): + pass + + def subscribe(self, storage_keys: List[StorageKey], subscription_handler: callable): + pass + + +class RuntimePalletInterface: + + def __init__(self, runtime_interface: 'RuntimeInterface', name: str): + self.runtime_interface = runtime_interface + self.name = name + + def get_metadata_obj(self) -> 'GenericPalletMetadata': + return self.runtime_interface.substrate.metadata.get_metadata_pallet(self.name) + + def call(self, name) -> RuntimeCallInterface: + return RuntimeCallInterface(self, name) -__all__ = ['ExtensionInterface'] + def storage(self, name: str) -> StorageFunctionInterface: + return StorageFunctionInterface(self, name) + + def constant(self, name): + pass + + +class RuntimeApiCallInterface: + + def __init__(self, runtime_api_interface: 'RuntimeApiInterface', name: str): + self.runtime_api_interface = runtime_api_interface + self.name = name + + def execute(self, *args): + raise NotImplementedError() + + def get_param_info(self): + raise NotImplementedError() + + +class RuntimeApiInterface: + + def __init__(self, runtime_interface: 'RuntimeInterface', name: str): + self.runtime_interface = runtime_interface + self.name = name + + def call(self, name) -> RuntimeApiCallInterface: + return RuntimeApiCallInterface(self, name) + + def list(self): + raise NotImplementedError() + + +class RuntimeInterface: + + def __init__(self, substrate: 'SubstrateInterface', block_hash: str = None): + self.substrate = substrate + self.config = substrate.runtime_config + self.block_hash = block_hash + + def init(self, block_hash: str = None): + if block_hash: + self.block_hash = block_hash + + # TODO move implementation of init here + self.substrate.init_runtime(block_hash=self.block_hash) + + def at(self, block_hash: str): + if block_hash is None: + block_hash = self.substrate.get_chain_head() + + self.init(block_hash=block_hash) + return self + + def create_scale_type(self, type_string: str, data: ScaleBytes = None) -> ScaleType: + self.init() + return self.config.create_scale_object(type_string=type_string, data=data) + + def pallet(self, name: str) -> RuntimePalletInterface: + return RuntimePalletInterface(self, name) + + def get_spec_version(self): + raise NotImplementedError() + + # def api_call(self, api, name): + # pass + + def api(self, name) -> RuntimeApiInterface: + return RuntimeApiInterface(self, name) + + def subscribe_storage(self, storage_keys): + raise NotImplementedError() + + @property + def storage(self): + return StorageInterface(self) + + @property + def metadata(self): + self.init() + return self.substrate.metadata + + +class BlockInterface: + + def __init__(self, substrate: 'SubstrateInterface'): + self.substrate = substrate + self.block_hash = None + + # def __init__(self, chain_interface: 'ChainInterface', block_hash: str): + # self.chain_interface = chain_interface + # self.block_hash = block_hash + + def number(self, block_number: int): + return self.at(self.substrate.get_block_hash(block_number)) + + def at(self, block_hash: str): + self.block_hash = block_hash + return self + + def extrinsics(self): + return self.substrate.get_extrinsics(block_hash=self.block_hash) + + def header(self): + pass + + def author(self): + pass + + def events(self): + return self.substrate.runtime.at(self.block_hash).pallet("System").storage("Events").get() + + +class ChainInterface: + def __init__(self, substrate: 'SubstrateInterface'): + self.substrate = substrate + + def get_block_hash(self, block_number: int = None): + return self.substrate.get_block_hash(block_number) + + def block(self): + return BlockInterface(self.substrate) class ExtensionInterface: @@ -106,3 +524,109 @@ def get_extension_callable(self, name: str) -> Callable: def __getattr__(self, name): return self.get_extension_callable(name) + + +class ContractMetadataInterface: + + def __init__(self, contract_interface): + self.contract_interface = contract_interface + + def create_from_file(self, metadata_file: str) -> "ContractMetadata": + return ContractMetadata.create_from_file( + metadata_file=metadata_file, substrate=self.contract_interface.substrate + ) + + +class ContractInstanceInterface: + def __init__(self, contract_bundle_interface, address: str): + self.contract_bundle_interface = contract_bundle_interface + self.address = address + + + + + +class ContractBundleInterface: + + def __init__(self, contract_interface, bundle_data: dict): + self.contract_interface = contract_interface + self.bundle_data = bundle_data + + def deploy(self): + pass + + def instantiate(self, keypair: Keypair, constructor: str, args: dict = None, value: int = 0, gas_limit: dict = None, + deployment_salt: str = None, upload_code: bool = False, storage_deposit_limit: int = None): + pass + + def instance(self, address: str): + return ContractInstanceInterface(self, address) + + +class ContractInterface: + + def __init__(self, substrate): + self.substrate = substrate + + def metadata(self): + return ContractMetadataInterface(self) + + # def instance(self, contract_address, metadata_file): + def bundle(self, bundle_data: dict): + return ContractBundleInterface(self, bundle_data) + +class QueryMapResult: + + def __init__(self, records: list, page_size: int, module: str = None, storage_function: str = None, + params: list = None, block_hash: str = None, substrate: 'SubstrateInterface' = None, + last_key: str = None, max_results: int = None, ignore_decoding_errors: bool = False): + self.current_index = -1 + self.records = records + self.page_size = page_size + self.module = module + self.storage_function = storage_function + self.block_hash = block_hash + self.substrate = substrate + self.last_key = last_key + self.max_results = max_results + self.params = params + self.ignore_decoding_errors = ignore_decoding_errors + self.loading_complete = False + + def retrieve_next_page(self, start_key) -> list: + if not self.substrate: + return [] + + result = self.substrate.query_map(module=self.module, storage_function=self.storage_function, + params=self.params, page_size=self.page_size, block_hash=self.block_hash, + start_key=start_key, max_results=self.max_results, + ignore_decoding_errors=self.ignore_decoding_errors) + + # Update last key from new result set to use as offset for next page + self.last_key = result.last_key + + return result.records + + def __iter__(self): + self.current_index = -1 + return self + + def __next__(self): + self.current_index += 1 + + if self.max_results is not None and self.current_index >= self.max_results: + self.loading_complete = True + raise StopIteration + + if self.current_index >= len(self.records) and not self.loading_complete: + # try to retrieve next page from node + self.records += self.retrieve_next_page(start_key=self.last_key) + + if self.current_index >= len(self.records): + self.loading_complete = True + raise StopIteration + + return self.records[self.current_index] + + def __getitem__(self, item): + return self.records[item] diff --git a/substrateinterface/storage.py b/substrateinterface/storage.py index 4f585d6..e3a78a8 100644 --- a/substrateinterface/storage.py +++ b/substrateinterface/storage.py @@ -81,7 +81,7 @@ def create_from_data(cls, data: bytes, runtime_config: RuntimeConfigurationObjec value_scale_type = storage_item.get_value_type_string() return cls( - pallet=None, storage_function=None, params=None, + pallet=pallet, storage_function=storage_function, params=None, data=data, metadata=metadata, value_scale_type=value_scale_type, runtime_config=runtime_config ) diff --git a/test/fixtures/flipper.contract b/test/fixtures/flipper.contract new file mode 100644 index 0000000..603084f --- /dev/null +++ b/test/fixtures/flipper.contract @@ -0,0 +1 @@ +{"source":{"hash":"0x0061915a553a27cebcfc9c4f0f15cdb228c5e137be4ed4d70a4f78846a11475e","language":"ink! 4.2.0","compiler":"rustc 1.69.0-nightly","wasm":"0x0061736d0100000001450c60027f7f017f60037f7f7f017f60027f7f0060037f7f7f0060017f0060047f7f7f7f017f60000060047f7f7f7f0060017f017f60017f017e60057f7f7f7f7f006000017f028a0107057365616c310b6765745f73746f726167650005057365616c301176616c75655f7472616e736665727265640002057365616c3005696e7075740002057365616c300d64656275675f6d6573736167650000057365616c320b7365745f73746f726167650005057365616c300b7365616c5f72657475726e000303656e76066d656d6f7279020102100337360102030b0803040002080102020604030202060600010103000300070204060202000400040900000a0507050000030a01000000000704050170010f0f0608017f01418080040b0711020463616c6c0018066465706c6f7900190914010041010b0e0d32273a29333738281c1e20392b0ac644362b01017f037f2002200346047f200005200020036a200120036a2d00003a0000200341016a21030c010b0b0b2601017f230041106b22022400200220003a000f20012002410f6a41011008200241106a24000b5c01037f02402000280208220420026a220320044f04402003200028020422054b0d01200028020020046a200320046b2001200241d897041035200020033602080f0b41809604411c41b89704101f000b2003200541c89704100b000b5502027f027e230041206b22002400200041106a22014200370300200042003703082000411036021c200041086a2000411c6a10012001290300210220002903082103200041206a2400410541042002200384501b0b1b002000418180014f044020004180800141e08104100b000b20000b7501017f230041306b220324002003200136020420032000360200200341146a41023602002003411c6a41023602002003412c6a41033602002003419c8f0436021020034100360208200341033602242003200341206a3602182003200341046a36022820032003360220200341086a2002100e000b5201017f230041206b220124002001410c6a4101360200200141146a4101360200200141f49404360208200141003602002001410136021c200120003602182001200141186a360210200141cc8204100e000b910101017f230041306b22022400200241146a41013602002002411c6a4101360200200241f494043602102002410036020820024102360224200220002d0000410274220041a49a046a28020036022c2002200041b89a046a280200360228200141046a28020021002002200241206a3602182002200241286a36022020012802002000200241086a1036200241306a24000b3c01017f230041206b22022400200241013a00182002200136021420022000360210200241e08a0436020c200241f49504360208200241086a102a000b4001017f230041106b22012400200141003a000f20002001410f6a41011010047f4102054101410220012d000f22004101461b410020001b0b200141106a24000b6001047f230041106b22032400200028020422042002492205450440200341086a4100200220002802002206103b200120022003280208200328020c41949a0410352003200220042006103b200020032903003702000b200341106a240020050b4701017f230041106b220224002002410036020c024020012002410c6a410410104504402000200228020c360001200041003a00000c010b200041013a00000b200241106a24000b3f01017f230041106b22022400200242808001370204200241d49a0436020020022001047f20024101101741010541000b101720002002280208100a1016000b3701017f230041106b22002400200042808001370204200041d49a0436020020004100101720004100101741002000280208100a1016000bae0102057f017e230041306b2201240020014100360218200142808001370224200141d49a043602202001410036021c200141206a22022001411c6a4104100820012001290320370310200141086a200141106a220320012802281015200128020c2104200128020820012903102106200141003602282001200637032020002002100720012001290320370310200120032001280228101520042001280200200128020410041a200141306a24000b4501017f2002200128020422034b0440419c9604412341f89704101f000b2001200320026b36020420012001280200220120026a36020020002002360204200020013602000b0d00200041d49a0420011005000ba10101027f20002802082202200028020422034904402000200241016a360208200028020020026a20013a00000f0b230041306b220024002000200336020420002002360200200041146a41023602002000411c6a41023602002000412c6a4103360200200041b48a0436021020004100360208200041033602242000200041206a360218200020003602282000200041046a360220200041086a41e89704100e000bab0501077f230041406a22002400024002400240024002400240100941ff0171410546044020004180800136022041d49a04200041206a100220002802202201418180014f0d0120002001360224200041d49a04360220200041106a200041206a101120002d00100d0520002800112201411876210220014110762104200141087621030240200141ff01712201412f470440200141e30047200341ff0171413a4772200441ff017141a50147720d0741012101200241d100460d010c070b200341ff017141860147200441ff017141db0047720d0641002101200241d901470d060b20004100360218200042808001370224200041d49a043602202000410036023c200041206a22032000413c6a4104100820002000290320370310200041086a200041106a20002802281015200028020c210520002802082000280210210220002000280214220436022020052002200310002103200420002802202205490d02024002400240410c20032003410c4f1b0e0402000001000b2000412c6a4101360200200041346a41003602002000418c8204360228200041f4950436023020004100360220200041206a41948204100e000b2000412c6a4101360200200041346a4100360200200041a483043602280c070b2000200536022420002002360220200041206a100f41ff017122024102460d042001450d032002451014410041001012000b200041043a0020200041206a100c000b20014180800141e88004100b000b2005200441e88004100b000b230041106b22002400200042808001370204200041d49a0436020020004100101720024100472000100741002000280208100a1016000b2000412c6a4101360200200041346a41003602002000418483043602280c010b410141011012000b200041f4950436023020004100360220200041206a41cc8204100e000b8b0201057f230041106b2200240002400240100941ff01712201410546044020004180800136020041d49a042000100220002802002201418180014f0d0120002001360204200041d49a04360200200041086a20001011024020002d00080d002000280009220141187621022001411076210320014108762104200141ff01712201419b01470440200141ed0147200441ff017141cb004772200341ff0171419d01472002411b4772720d01410010141013000b200441ff017141ae0147200341ff0171419d014772200241de0047720d002000100f41ff017122004102470d030b410141011012000b200020013a00002000100c000b20014180800141e88004100b000b200010141013000b5501017f230041206b2202240020022000360204200241186a200141106a290200370300200241106a200141086a29020037030020022001290200370308200241046a41ac8304200241086a101b200241206a24000bee0301057f230041406a22032400200341033a003820034280808080800437033020034100360228200341003602202003200136021c20032000360218027f0240024020022802002201450440200241146a28020022004103742105200041ffffffff017121072002280210210441002101034020012005460d02200228020820016a220041046a28020022060440200328021820002802002006200328021c28020c1101000d040b200141086a2101200428020020042802042106200441086a2104200341186a2006110000450d000b0c020b200228020422074105742100200741ffffff3f71210703402000450d01200228020820046a220541046a28020022060440200328021820052802002006200328021c28020c1101000d030b20032001411c6a2d00003a00382003200141146a290200370330200341106a200228021022052001410c6a103420032003290310370320200341086a2005200141046a103420032003290308370328200441086a2104200041206b210020012802002106200141206a2101200520064103746a2205280200200341186a2005280204110000450d000b0c010b2002410c6a28020020074b04402003280218200228020820074103746a22002802002000280204200328021c28020c1101000d010b41000c010b41010b200341406b24000b0f00200028020020012002101d41000b7701027f230041106b2204240020022000280200200028020822036b4b0440200441086a20002003200210212004280208200428020c1022200028020821030b200028020420036a2001200210061a2003200220036a22014b044041d08304411c41b08904101f000b20002001360208200441106a24000bdd0201037f230041106b220224000240024002400240200028020022002002410c6a027f0240024020014180014f04402002410036020c2001418010490d012001418080044f0d0220022001413f71418001723a000e20022001410c7641e001723a000c20022001410676413f71418001723a000d41030c030b200028020822032000280200460d030c040b20022001413f71418001723a000d2002200141067641c001723a000c41020c010b20022001413f71418001723a000f20022001410676413f71418001723a000e20022001410c76413f71418001723a000d2002200141127641077141f001723a000c41040b101d0c020b230041106b22042400200441086a20002003410110212004280208200428020c1022200441106a2400200028020821030b200028020420036a20013a0000200341016a2201450d01200020013602080b200241106a240041000f0b41d08304411c41a08904101f000b5001017f230041206b220324002003410c6a4101360200200341146a4100360200200341f49504360210200341003602002003200136021c200320003602182003200341186a36020820032002100e000b4a01017f230041206b220224002000280200200241186a200141106a290200370300200241106a200141086a29020037030020022001290200370308200241086a101a200241206a24000bac0401067f230041206b2204240002402000027f4100200220036a22032002490d001a2001280200220220026a22062002490d0141082006200320032006491b2203200341084d1b2203417f73411f7621050240200204402004410136021820042002360214200420012802043602100c010b200441003602180b200441106a2107230041106b220624002004027f0240027f0240200504400240200341004e044020072802080d012006200310252006280204210220062802000c040b0c040b20072802042209450440200641086a20031025200628020c210220062802080c030b20032102410041cc9a04280200220520036a22082005490d021a2007280200210741d09a042802002008490440200341ffff036a220841107640002202417f46200241ffff0371200247720d022002411074220520084180807c716a22022005490d0241d09a042002360200200321024100200320056a22082005490d031a0b41cc9a04200836020041002005450d021a20052007200910060c020b200420033602040c020b2003210241000b2205044020042005360204200441086a200236020041000c020b20042003360204200441086a410136020041010c010b200441086a410036020041010b360200200641106a240020042802004504402004280204210220012003360200200120023602044181808080780c010b20042802042103200441086a2802000b36020420002003360200200441206a24000f0b41f08304412141948504101f000b1f00024020014181808080784704402001450d0120001023000b0f0b1024000b900101017f230041306b220124002001200036020c2001411c6a4102360200200141246a4101360200200141e08604360218200141003602102001410336022c2001200141286a36022020012001410c6a360228230041206b22002400200041003a0018200041f086043602142000200141106a360210200041e08a0436020c200041f49504360208200041086a102a000b4601017f230041206b22002400200041146a41013602002000411c6a4100360200200041b88504360210200041f4950436021820004100360208200041086a41c08504100e000ba10101027f027f410041cc9a04280200220220016a22032002490d001a024041d09a042802002003490440200141ffff036a22032001490d01200341107640002202417f46200241ffff0371200247720d012002411074220220034180807c716a22032002490d0141d09a0420033602004100200120026a22032002490d021a0b41cc9a04200336020020020c010b41000b210320002001360204200020033602000b5301027f230041106b2202240002402001450440410121030c010b200141004e0440200241086a20011025200228020822030d0120011023000b1024000b2000200336020420002001360200200241106a24000bd806020b7f027e230041406a2203240020002802002202ad210d0240024002400240024002400240024020024190ce004f044041272100200d210e0240034020004104490d01200341196a20006a220241046b200e200e4290ce0080220d4290ce007e7da7220441ffff037141e4006e220641017441918c046a2f00003b0000200241026b2004200641e4006c6b41ffff037141017441918c046a2f00003b0000200041046b2100200e42ffc1d72f56200d210e0d000b200da7220241e3004d0d0320004102490d090c020b0c080b41272100200241e3004b0d002002410a490d040c020b200041026b2200200341196a6a200da72202200241ffff037141e4006e220241e4006c6b41ffff037141017441918c046a2f00003b00000b2002410a490d01200041024f0d000c050b200041026b2200200341196a6a200241017441918c046a2f00003b00000c020b2000450d030b200041016b2200200341196a6a200241306a3a00000b200041274b0d01412820006b412720006b22062001280218220541017122071b2102410021042005410471044041f495042104200241f4950441f49504102c20026a22024b0d010b412b418080c40020071b2107200341196a20006a2108024020012802084504404101210020012802002202200141046a280200220120072004102f0d01200220082006200128020c11010021000c010b024020022001410c6a28020022094904402005410871450d01200128021c210b2001413036021c20012d0020210c41012100200141013a002020012802002205200141046a280200220a20072004102f0d02200341106a2001200920026b4101103020032802142202418080c400460d022003280210200520082006200a28020c1101000d0220022005200a10310d022001200c3a00202001200b36021c410021000c020b4101210020012802002202200141046a280200220120072004102f0d01200220082006200128020c11010021000c010b41012100200341086a2001200920026b41011030200328020c2205418080c400460d00200328020820012802002202200141046a280200220120072004102f0d00200220082006200128020c1101000d00200520022001103121000b200341406b240020000f0b41c08904411c41c88e04101f000b41e08904412141dc9404101f000b0300010b0e0020002802001a03400c000b000baa05020a7f017e230041406a220124002001200036020c2001412c6a4102360200200141346a4101360200200141c09604360228200141003602202001410436023c2001200141386a36023020012001410c6a360238200141106a210641002100230041306b22022400200141206a220441146a2802002107200428020821050240024002400240200241086a027f024002400240200241106a027f024002402004410c6a28020022080e020001040b20070d0241f49504210341000c010b20070d022005280200210320052802040b22001026200228021021042006200228021422053602042006200436020020052003200010061a200620003602080c040b200428021021090c010b200541046a21032008410374210a2004280210210903402000200020032802006a22004b0d04200341086a2103200a41086b220a0d000b20002007450d011a2000410f4b0d0041002005280204450d011a0b200020006a22034100200020034d1b0b10262002290308210b200641003602082006200b3702002002200736022c200220093602282002200836022420022005360220200220042902003703182006200241186a101a0d020b200241306a24000c020b41d08304411c41cc9404101f000b230041406a220024002000413336020c2000418087043602082000419484043602142000200241186a360210200041246a41023602002000412c6a41023602002000413c6a4106360200200041948b0436022020004100360218200041023602342000200041306a3602282000200041106a3602382000200041086a360230200041186a41a08804100e000b2001280214210020012802182101024041d49a052d000045044041d59a052d00004101710d010b410c20002001100322002000410c4f1b410947044041d49a0541013a00000b41d59a0541013a00000b000b0c0042dacb86e0f49b85f32b0ba704010a7f230041106b2203240002400240200020016b22024110490d002002200141036a417c7120016b220049200041044b720d00200220006b22044104490d0020012000102d2206200020016a22082004417c716a2004410371102d6a220220064f0440200441027621050240024003402005450d0520032008200541c0012005200541c0014f1b419c9004102e200328020c21052003280208210820032003280200200328020422002000417c7141849204102e200328020c210920032802082107024020032802042200450440410021010c010b2003280200220420004102746a210a4100210103402004220641106a2104410021000240034020012001200020066a280200220b417f73410776200b410676724181828408716a22014d0440200041046a22004110470d010c020b0b41c08904411c41949204101f000b2004200a470d000b0b20022002200141087641ff81fc0771200141ff81fc07716a418180046c4110766a22024b0d012009450d000b200941027421004100210103402001200120072802002204417f734107762004410676724181828408716a22014b0d02200741046a2107200041046b22000d000b20022002200141087641ff81fc0771200141ff81fc07716a418180046c4110766a22024d0d0441c08904411c41c49204101f000b41c08904411c41a49204101f000b41c08904411c41b49204101f000b41c08904411c41f49104101f000b20012002102d21020b200341106a240020020b4601017f200145044041000f0b024003402002200220002c000041bf7f4a6a22024b0d01200041016a2100200141016b22010d000b20020f0b41c08904411c41cc9404101f000b3e00200220034f044020002003360204200020013602002000410c6a200220036b3602002000200120034102746a3602080f0b419c960441232004101f000b39000240027f2002418080c40047044041012000200220012802101100000d011a0b20030d0141000b0f0b200020034100200128020c1101000bae0101027f20022104024002400240200320012d0020220320034103461b41ff0171220341016b0e03010001020b200241016a2203044020034101762104200241017621030c020b41c08904411c41d88e04101f000b41002104200221030b200341016a2102200128021c2103200128020421052001280200210102400340200241016b2202450d01200120032005280210110000450d000b418080c40021030b20002003360204200020043602000b3201017f027f0340200020002004460d011a200441016a2104200220012003280210110000450d000b200441016b0b2000490bea04010b7f230041106b2209240020002802042104200028020021030240024002402001280208220b410147200128021022024101477145044020024101470d02200320046a210c200141146a28020041016a210a410021022003210003402000200c460d03027f024020002c0000220641004e0440200041016a2105200641ff017121070c010b20002d0001413f7121052006411f7121072006415f4d044020074106742005722107200041026a21050c010b20002d0002413f7120054106747221082006417049044020082007410c74722107200041036a21050c010b200041046a210520022106418080c4002007411274418080f0007120002d0003413f71200841067472722207418080c400460d011a0b2002200520006b6a22062002490d0320070b2108200a41016b220a044020052100200621022008418080c400470d010c040b0b2008418080c400460d02024002402002450d00200220044f04404100210020022004460d010c020b41002100200220036a2c00004140480d010b200321000b2002200420001b21042000200320001b21030c020b200128020020032004200128020428020c11010021000c020b41c08904411c41c49304101f000b200b450440200128020020032004200128020428020c11010021000c010b2001410c6a2802002200200320046a2003102c22024b0440200941086a2001200020026b4100103041012100200928020c2202418080c400460d0120092802082001280200220520032004200141046a280200220128020c1101000d01200220052001103121000c010b200128020020032004200128020428020c11010021000b200941106a240020000b140020002802002001200028020428020c1100000b5501027f0240027f02400240200228020041016b0e020103000b200241046a0c010b200120022802044103746a22012802044105470d0120012802000b2802002104410121030b20002004360204200020033602000b8501002001200346044020002002200110061a0f0b230041306b220024002000200336020420002001360200200041146a41033602002000411c6a41023602002000412c6a4103360200200041ec900436021020004100360208200041033602242000200041206a360218200020003602282000200041046a360220200041086a2004100e000b4901017f230041206b22032400200341186a200241106a290200370300200341106a200241086a2902003703002003200229020037030820002001200341086a101b200341206a24000b5801027f230041206b22022400200128020421032001280200200241186a2000280200220041106a290200370300200241106a200041086a290200370300200220002902003703082003200241086a101b200241206a24000b0b002000280200200110320b1800200128020041ec94044105200128020428020c1101000b990301037f230041406a22022400200028020021034101210002402001280200220441f08a04410c200141046a280200220128020c1101000d0002402003280208220004402002200036020c200241346a4102360200410121002002413c6a4101360200200241808b0436023020024100360228200241073602142002200241106a36023820022002410c6a36021020042001200241286a1036450d010c020b20032802002200200328020428020c11090042c8b5e0cfca86dbd3897f520d002002200036020c200241346a4102360200410121002002413c6a4101360200200241808b0436023020024100360228200241083602142002200241106a36023820022002410c6a36021020042001200241286a10360d010b200328020c21002002411c6a4103360200200241246a41033602002002413c6a4103360200200241346a4103360200200241c88a043602182002410036021020022000410c6a3602382002200041086a3602302002410236022c200220003602282002200241286a36022020042001200241106a103621000b200241406b240020000b2c00200120024d04402000200220016b3602042000200120036a3602000f0b41909904412141fc9804101f000b0bc91a0300418080040bc1032f55736572732f61726a616e2f2e636172676f2f72656769737472792f7372632f6769746875622e636f6d2d316563633632393964623965633832332f696e6b5f656e762d342e322e302f7372632f656e67696e652f6f6e5f636861696e2f6578742e72730000000000010065000000e4000000140000002f55736572732f61726a616e2f2e636172676f2f72656769737472792f7372632f6769746875622e636f6d2d316563633632393964623965633832332f696e6b5f656e762d342e322e302f7372632f656e67696e652f6f6e5f636861696e2f696d706c732e72730078000100670000002401000023000000656e636f756e746572656420756e6578706563746564206572726f72f00001001c0000007800010067000000ed000000170000002f55736572732f61726a616e2f446576656c6f706d656e742f666c69707065722f6c69622e72730024010100270000000600000005000000636f756c64206e6f742070726f7065726c79206465636f64652073746f7261676520656e747279005c0101002700000073746f7261676520656e7472792077617320656d707479008c010100170000000900000004000000040000000a0000000b0000000c0041d083040ba412617474656d707420746f206164642077697468206f766572666c6f7700000000617474656d707420746f206d756c7469706c792077697468206f766572666c6f770000000900000000000000010000000d0000002f55736572732f61726a616e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f616c6c6f632f7372632f7261775f7665632e72730000240201006e0000008f0100001c0000006361706163697479206f766572666c6f77000000a402010011000000240201006e0000000d020000050000002f55736572732f61726a616e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f616c6c6f632f7372632f616c6c6f632e72736d656d6f727920616c6c6f636174696f6e206f6620206279746573206661696c656400003c03010015000000510301000d000000d00201006c0000009f0100000d0000006120666f726d617474696e6720747261697420696d706c656d656e746174696f6e2072657475726e656420616e206572726f722f55736572732f61726a616e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f616c6c6f632f7372632f666d742e7273000000b30301006a00000064020000200000002f55736572732f61726a616e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f616c6c6f632f7372632f7665632f6d6f642e72730000300401006e000000350700000d000000300401006e000000a307000009000000617474656d707420746f206164642077697468206f766572666c6f7700000000617474656d707420746f2073756274726163742077697468206f766572666c6f7729696e646578206f7574206f6620626f756e64733a20746865206c656e20697320206275742074686520696e64657820697320020501002000000022050100120000003a000000f40a010000000000440501000100000044050100010000000900000000000000010000000e00000070616e69636b65642061742027272c207c050100010000007d050100030000003a200000f40a01000000000090050100020000002f55736572732f61726a616e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f636f72652f7372632f666d742f6e756d2e727330303031303230333034303530363037303830393130313131323133313431353136313731383139323032313232323332343235323632373238323933303331333233333334333533363337333833393430343134323433343434353436343734383439353035313532353335343535353635373538353936303631363236333634363536363637363836393730373137323733373437353736373737383739383038313832383338343835383638373838383939303931393239333934393539363937393839392f55736572732f61726a616e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f636f72652f7372632f666d742f6d6f642e72730000d90601006d000000750500000d000000d90601006d0000000506000038000000206f7574206f662072616e676520666f7220736c696365206f66206c656e6774682072616e676520656e6420696e6465782000008a0701001000000068070100220000002f55736572732f61726a616e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f636f72652f7372632f736c6963652f697465722e7273ac07010070000000c005000025000000736f7572636520736c696365206c656e67746820282920646f6573206e6f74206d617463682064657374696e6174696f6e20736c696365206c656e67746820282c08010015000000410801002b00000001050100010000002f55736572732f61726a616e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f636f72652f7372632f7374722f636f756e742e727300840801006f0000004700000015000000840801006f0000004f00000032000000840801006f0000005400000011000000840801006f0000005a00000009000000840801006f0000006400000011000000840801006f000000660000000d0000002f55736572732f61726a616e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f636f72652f7372632f7374722f697465722e72730000540901006e00000091000000110000002f55736572732f61726a616e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f636f72652f7372632f697465722f7472616974732f616363756d2e727300d4090100770000009500000001000000a40501006d000000cd010000050000004572726f72000000f40a0100000000007061696420616e20756e70617961626c65206d657373616765636f756c64206e6f74207265616420696e707574756e61626c6520746f206465636f646520696e707574656e636f756e746572656420756e6b6e6f776e2073656c6563746f72756e61626c6520746f206465636f64652073656c6563746f7200418096040bcb04617474656d707420746f206164642077697468206f766572666c6f77617373657274696f6e206661696c65643a206d6964203c3d2073656c662e6c656e28290af40a0100000000003f0b0100010000002f55736572732f61726a616e2f2e636172676f2f72656769737472792f7372632f6769746875622e636f6d2d316563633632393964623965633832332f696e6b5f656e762d342e322e302f7372632f656e67696e652f6f6e5f636861696e2f6275666665722e7273500b0100680000005a0000001c000000500b0100680000005a00000009000000500b0100680000005a00000031000000500b0100680000006500000009000000500b0100680000008d000000210000002f55736572732f61726a616e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f636f72652f7372632f736c6963652f696e6465782e7273000000080c010071000000820100004700000000000000617474656d707420746f2073756274726163742077697468206f766572666c6f772f55736572732f61726a616e2f2e636172676f2f72656769737472792f7372632f6769746875622e636f6d2d316563633632393964623965633832332f7061726974792d7363616c652d636f6465632d332e352e302f7372632f636f6465632e727300b10c010062000000780000000e000000190000001c000000160000001400000019000000db0a0100bf0a0100a90a0100950a01007c0a01","build_info":{"build_mode":"Debug","cargo_contract_version":"2.2.1","rust_toolchain":"nightly-aarch64-apple-darwin","wasm_opt_settings":{"keep_debug_symbols":false,"optimization_passes":"Z"}}},"contract":{"name":"flipper","version":"0.1.0","authors":["[your_name] <[your_email]>"]},"spec":{"constructors":[{"args":[{"label":"init_value","type":{"displayName":["bool"],"type":0}}],"default":false,"docs":["Constructor that initializes the `bool` value to the given `init_value`."],"label":"new","payable":false,"returnType":{"displayName":["ink_primitives","ConstructorResult"],"type":1},"selector":"0x9bae9d5e"},{"args":[],"default":false,"docs":["Constructor that initializes the `bool` value to `false`.","","Constructors can delegate to other constructors."],"label":"default","payable":false,"returnType":{"displayName":["ink_primitives","ConstructorResult"],"type":1},"selector":"0xed4b9d1b"}],"docs":[],"environment":{"accountId":{"displayName":["AccountId"],"type":5},"balance":{"displayName":["Balance"],"type":8},"blockNumber":{"displayName":["BlockNumber"],"type":11},"chainExtension":{"displayName":["ChainExtension"],"type":12},"hash":{"displayName":["Hash"],"type":9},"maxEventTopics":4,"timestamp":{"displayName":["Timestamp"],"type":10}},"events":[],"lang_error":{"displayName":["ink","LangError"],"type":3},"messages":[{"args":[],"default":false,"docs":[" A message that can be called on instantiated contracts."," This one flips the value of the stored `bool` from `true`"," to `false` and vice versa."],"label":"flip","mutates":true,"payable":false,"returnType":{"displayName":["ink","MessageResult"],"type":1},"selector":"0x633aa551"},{"args":[],"default":false,"docs":[" Simply returns the current value of our `bool`."],"label":"get","mutates":false,"payable":false,"returnType":{"displayName":["ink","MessageResult"],"type":4},"selector":"0x2f865bd9"}]},"storage":{"root":{"layout":{"struct":{"fields":[{"layout":{"leaf":{"key":"0x00000000","ty":0}},"name":"value"}],"name":"Flipper"}},"root_key":"0x00000000"}},"types":[{"id":0,"type":{"def":{"primitive":"bool"}}},{"id":1,"type":{"def":{"variant":{"variants":[{"fields":[{"type":2}],"index":0,"name":"Ok"},{"fields":[{"type":3}],"index":1,"name":"Err"}]}},"params":[{"name":"T","type":2},{"name":"E","type":3}],"path":["Result"]}},{"id":2,"type":{"def":{"tuple":[]}}},{"id":3,"type":{"def":{"variant":{"variants":[{"index":1,"name":"CouldNotReadInput"}]}},"path":["ink_primitives","LangError"]}},{"id":4,"type":{"def":{"variant":{"variants":[{"fields":[{"type":0}],"index":0,"name":"Ok"},{"fields":[{"type":3}],"index":1,"name":"Err"}]}},"params":[{"name":"T","type":0},{"name":"E","type":3}],"path":["Result"]}},{"id":5,"type":{"def":{"composite":{"fields":[{"type":6,"typeName":"[u8; 32]"}]}},"path":["ink_primitives","types","AccountId"]}},{"id":6,"type":{"def":{"array":{"len":32,"type":7}}}},{"id":7,"type":{"def":{"primitive":"u8"}}},{"id":8,"type":{"def":{"primitive":"u128"}}},{"id":9,"type":{"def":{"composite":{"fields":[{"type":6,"typeName":"[u8; 32]"}]}},"path":["ink_primitives","types","Hash"]}},{"id":10,"type":{"def":{"primitive":"u64"}}},{"id":11,"type":{"def":{"primitive":"u32"}}},{"id":12,"type":{"def":{"variant":{}},"path":["ink_env","types","NoChainExtension"]}}],"version":"4"} diff --git a/test/test_create_extrinsics.py b/test/test_create_extrinsics.py index 84ec4c2..5994897 100644 --- a/test/test_create_extrinsics.py +++ b/test/test_create_extrinsics.py @@ -49,15 +49,10 @@ def setUpClass(cls): cls.keypair = Keypair.create_from_mnemonic(mnemonic) def test_create_extrinsic_metadata_v14(self): - # Create balance transfer call - call = self.kusama_substrate.compose_call( - call_module='Balances', - call_function='transfer', - call_params={ - 'dest': 'EaG2CRhJWPb7qmdcJvy3LiWdh26Jreu9Dx6R1rXxPmYXoDk', - 'value': 3 * 10 ** 3 - } + call = self.kusama_substrate.runtime.pallet("Balances").call("transfer").create( + dest='EaG2CRhJWPb7qmdcJvy3LiWdh26Jreu9Dx6R1rXxPmYXoDk', + value=3 * 10 ** 3 ) extrinsic = self.kusama_substrate.create_signed_extrinsic(call=call, keypair=self.keypair, tip=1) @@ -75,13 +70,9 @@ def test_create_mortal_extrinsic(self): for substrate in [self.kusama_substrate, self.polkadot_substrate]: # Create balance transfer call - call = substrate.compose_call( - call_module='Balances', - call_function='transfer', - call_params={ - 'dest': 'EaG2CRhJWPb7qmdcJvy3LiWdh26Jreu9Dx6R1rXxPmYXoDk', - 'value': 3 * 10 ** 3 - } + call = self.kusama_substrate.runtime.pallet("Balances").call("transfer").create( + dest='EaG2CRhJWPb7qmdcJvy3LiWdh26Jreu9Dx6R1rXxPmYXoDk', + value=3 * 10 ** 3 ) extrinsic = substrate.create_signed_extrinsic(call=call, keypair=self.keypair, era={'period': 64}) @@ -97,21 +88,13 @@ def test_create_mortal_extrinsic(self): def test_create_batch_extrinsic(self): - balance_call = self.polkadot_substrate.compose_call( - call_module='Balances', - call_function='transfer', - call_params={ - 'dest': 'EaG2CRhJWPb7qmdcJvy3LiWdh26Jreu9Dx6R1rXxPmYXoDk', - 'value': 3 * 10 ** 3 - } + balance_call = self.polkadot_substrate.runtime.pallet("Balances").call("transfer").create( + dest='EaG2CRhJWPb7qmdcJvy3LiWdh26Jreu9Dx6R1rXxPmYXoDk', + value=3 * 10 ** 3 ) - call = self.polkadot_substrate.compose_call( - call_module='Utility', - call_function='batch', - call_params={ - 'calls': [balance_call, balance_call] - } + call = self.polkadot_substrate.runtime.pallet("Utility").call("batch").create( + calls=[balance_call, balance_call] ) extrinsic = self.polkadot_substrate.create_signed_extrinsic(call=call, keypair=self.keypair, era={'period': 64}) diff --git a/test/test_runtime_call.py b/test/test_runtime_call.py index 0ff36ea..3683c68 100644 --- a/test/test_runtime_call.py +++ b/test/test_runtime_call.py @@ -36,7 +36,8 @@ def setUpClass(cls): cls.keypair = Keypair.create_from_mnemonic(mnemonic) def test_core_version(self): - result = self.substrate.runtime_call("Core", "version") + # result = self.substrate.runtime_call("Core", "version") + result = self.substrate.runtime.api("Core").call("version").execute() self.assertGreater(result.value['spec_version'], 0) self.assertEqual('polkadot', result.value['spec_name']) @@ -69,8 +70,11 @@ def test_transaction_payment(self): def test_metadata_call_info(self): - runtime_call = self.substrate.get_metadata_runtime_call_function("TransactionPaymentApi", "query_fee_details") - param_info = runtime_call.get_param_info() + #runtime_call = self.substrate.get_metadata_runtime_call_function("TransactionPaymentApi", "query_fee_details") + #param_info = runtime_call.get_param_info() + + param_info = self.substrate.runtime.api("TransactionPaymentApi").call("query_fee_details").get_param_info() + self.assertEqual('Extrinsic', param_info[0]) self.assertEqual('u32', param_info[1]) @@ -90,7 +94,7 @@ def test_check_all_runtime_call_types(self): def test_unknown_runtime_call(self): with self.assertRaises(ValueError): - self.substrate.runtime_call("Foo", "bar") + self.substrate.runtime.api("Foo").call("bar").execute() if __name__ == '__main__': diff --git a/test/test_runtime_interface.py b/test/test_runtime_interface.py new file mode 100644 index 0000000..673bd3d --- /dev/null +++ b/test/test_runtime_interface.py @@ -0,0 +1,161 @@ +# Polkascan API extension for Substrate Interface Library +# +# Copyright 2018-2023 Stichting Polkascan (Polkascan Foundation). +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# Python Substrate Interface Library +# +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import unittest +from unittest.mock import MagicMock + +from substrateinterface import SubstrateInterface +from substrateinterface.exceptions import StorageFunctionNotFound +from test import settings + + +class RuntimeInterfaceTestCase(unittest.TestCase): + + @classmethod + def setUpClass(cls): + cls.substrate = SubstrateInterface( + url=settings.KUSAMA_NODE_URL + ) + + def test_system_account(self): + + result = self.substrate.runtime.at( + "0x176e064454388fd78941a0bace38db424e71db9d5d5ed0272ead7003a02234fa" + ).pallet("System").storage("Account").get("F4xQKRUagnSGjFqafyhajLs94e7Vvzvr8ebwYJceKpr8R7T") + + self.assertEqual(7673, result.value['nonce']) + self.assertEqual(637747267365404068, result.value['data']['free']) + self.assertEqual(result.meta_info['result_found'], True) + + def test_create_call(self): + call = self.substrate.runtime.pallet("Balances").call("transfer").create( + dest='F4xQKRUagnSGjFqafyhajLs94e7Vvzvr8ebwYJceKpr8R7T', + value=2131234324 + ) + self.assertEqual(call.value['call_args']['value'], 2131234324) + self.assertEqual(call.value['call_args']['dest'], 'F4xQKRUagnSGjFqafyhajLs94e7Vvzvr8ebwYJceKpr8R7T') + + def test_claims_claim_map(self): + + result = self.substrate.runtime.pallet("Claims").storage("Claims").list(max_results=3) + + records = [item for item in result] + + self.assertEqual(3, len(records)) + self.assertEqual(45880000000000, records[0][1].value) + self.assertEqual('0x00000a9c44f24e314127af63ae55b864a28d7aee', records[0][0].value) + self.assertEqual('0x00002f21194993a750972574e2d82ce8c95078a6', records[1][0].value) + self.assertEqual('0x0000a940f973ccf435ae9c040c253e1c043c5fb2', records[2][0].value) + + def test_system_account_non_existing(self): + result = self.substrate.runtime.pallet("System").storage("Account").get("GSEX8kR4Kz5UZGhvRUCJG93D5hhTAoVZ5tAe6Zne7V42DSi") + + # result = self.kusama_substrate.query( + # module='System', + # storage_function='Account', + # params=['GSEX8kR4Kz5UZGhvRUCJG93D5hhTAoVZ5tAe6Zne7V42DSi'] + # ) + + self.assertEqual( + { + 'nonce': 0, 'consumers': 0, 'providers': 0, 'sufficients': 0, + 'data': { + 'free': 0, 'reserved': 0, 'misc_frozen': 0, 'fee_frozen': 0 + } + }, result.value) + + + def test_block_extrinsics(self): + + extrinsics = self.substrate.block.hash(100).extrinsics() + print(extrinsics) + + # + # def test_non_existing_query(self): + # with self.assertRaises(StorageFunctionNotFound) as cm: + # self.kusama_substrate.query("Unknown", "StorageFunction") + # + # self.assertEqual('Pallet "Unknown" not found', str(cm.exception)) + # + # def test_missing_params(self): + # with self.assertRaises(ValueError) as cm: + # self.kusama_substrate.query("System", "Account") + # + # def test_modifier_default_result(self): + # result = self.kusama_substrate.query( + # module='Staking', + # storage_function='HistoryDepth', + # block_hash='0x4b313e72e3a524b98582c31cd3ff6f7f2ef5c38a3c899104a833e468bb1370a2' + # ) + # + # self.assertEqual(84, result.value) + # self.assertEqual(result.meta_info['result_found'], False) + # + # def test_modifier_option_result(self): + # + # result = self.kusama_substrate.query( + # module='Identity', + # storage_function='IdentityOf', + # params=["DD6kXYJPHbPRbBjeR35s1AR7zDh7W2aE55EBuDyMorQZS2a"], + # block_hash='0x4b313e72e3a524b98582c31cd3ff6f7f2ef5c38a3c899104a833e468bb1370a2' + # ) + # + # self.assertIsNone(result.value) + # self.assertEqual(result.meta_info['result_found'], False) + # + # def test_identity_hasher(self): + # result = self.kusama_substrate.query("Claims", "Claims", ["0x00000a9c44f24e314127af63ae55b864a28d7aee"]) + # self.assertEqual(45880000000000, result.value) + # + # def test_well_known_keys_result(self): + # result = self.kusama_substrate.query("Substrate", "Code") + # self.assertIsNotNone(result.value) + # + # def test_well_known_keys_default(self): + # result = self.kusama_substrate.query("Substrate", "HeapPages") + # self.assertEqual(0, result.value) + # + # def test_well_known_keys_not_found(self): + # with self.assertRaises(StorageFunctionNotFound): + # self.kusama_substrate.query("Substrate", "Unknown") + # + # def test_well_known_pallet_version(self): + # + # sf = self.kusama_substrate.get_metadata_storage_function("System", "PalletVersion") + # self.assertEqual(sf.value['name'], ':__STORAGE_VERSION__:') + # + # result = self.kusama_substrate.query("System", "PalletVersion") + # self.assertGreaterEqual(result.value, 0) + + +if __name__ == '__main__': + unittest.main()