diff --git a/examples/assets/flipper-v5.json b/examples/assets/flipper-v5.json new file mode 100644 index 0000000..73a10f1 --- /dev/null +++ b/examples/assets/flipper-v5.json @@ -0,0 +1,463 @@ +{ + "source": { + "hash": "0x04208d5b3de1808ed1ccf83d08bdb5e3974bba7c4af8d5fc1ed6d6c725155c2e", + "language": "ink! 5.0.0", + "compiler": "rustc 1.81.0-nightly", + "build_info": { + "build_mode": "Debug", + "cargo_contract_version": "4.1.1", + "rust_toolchain": "nightly-aarch64-apple-darwin", + "wasm_opt_settings": { + "keep_debug_symbols": false, + "optimization_passes": "Z" + } + } + }, + "contract": { + "name": "flipper5", + "version": "0.1.0", + "authors": [ + "[your_name] <[your_email]>" + ] + }, + "image": null, + "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": 2 + }, + "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": 2 + }, + "selector": "0xed4b9d1b" + } + ], + "docs": [], + "environment": { + "accountId": { + "displayName": [ + "AccountId" + ], + "type": 7 + }, + "balance": { + "displayName": [ + "Balance" + ], + "type": 9 + }, + "blockNumber": { + "displayName": [ + "BlockNumber" + ], + "type": 12 + }, + "chainExtension": { + "displayName": [ + "ChainExtension" + ], + "type": 13 + }, + "hash": { + "displayName": [ + "Hash" + ], + "type": 10 + }, + "maxEventTopics": 4, + "staticBufferSize": 16384, + "timestamp": { + "displayName": [ + "Timestamp" + ], + "type": 11 + } + }, + "events": [ + { + "args": [ + { + "docs": [], + "indexed": true, + "label": "value", + "type": { + "displayName": [ + "bool" + ], + "type": 0 + } + } + ], + "docs": [], + "label": "Flipped", + "module_path": "flipper5::flipper5", + "signature_topic": "0x529cf346ddea0543633a1d91f021fa688fb7fe023ee1fb83ad031fe005673254" + }, + { + "args": [ + { + "docs": [], + "indexed": true, + "label": "test_value", + "type": { + "displayName": [ + "u8" + ], + "type": 6 + } + } + ], + "docs": [], + "label": "Test", + "module_path": "flipper5::flipper5", + "signature_topic": "0xc04204b5a8f12647ea7e92832f7608f4b5279fbcd2181333ff6a96906e5d555f" + } + ], + "lang_error": { + "displayName": [ + "ink", + "LangError" + ], + "type": 4 + }, + "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": 2 + }, + "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": 5 + }, + "selector": "0x2f865bd9" + } + ] + }, + "storage": { + "root": { + "layout": { + "struct": { + "fields": [ + { + "layout": { + "leaf": { + "key": "0x00000000", + "ty": 0 + } + }, + "name": "value" + } + ], + "name": "Flipper5" + } + }, + "root_key": "0x00000000", + "ty": 1 + } + }, + "types": [ + { + "id": 0, + "type": { + "def": { + "primitive": "bool" + } + } + }, + { + "id": 1, + "type": { + "def": { + "composite": { + "fields": [ + { + "name": "value", + "type": 0, + "typeName": ",>>::Type" + } + ] + } + }, + "path": [ + "flipper5", + "flipper5", + "Flipper5" + ] + } + }, + { + "id": 2, + "type": { + "def": { + "variant": { + "variants": [ + { + "fields": [ + { + "type": 3 + } + ], + "index": 0, + "name": "Ok" + }, + { + "fields": [ + { + "type": 4 + } + ], + "index": 1, + "name": "Err" + } + ] + } + }, + "params": [ + { + "name": "T", + "type": 3 + }, + { + "name": "E", + "type": 4 + } + ], + "path": [ + "Result" + ] + } + }, + { + "id": 3, + "type": { + "def": { + "tuple": [] + } + } + }, + { + "id": 4, + "type": { + "def": { + "variant": { + "variants": [ + { + "index": 1, + "name": "CouldNotReadInput" + } + ] + } + }, + "path": [ + "ink_primitives", + "LangError" + ] + } + }, + { + "id": 5, + "type": { + "def": { + "variant": { + "variants": [ + { + "fields": [ + { + "type": 0 + } + ], + "index": 0, + "name": "Ok" + }, + { + "fields": [ + { + "type": 4 + } + ], + "index": 1, + "name": "Err" + } + ] + } + }, + "params": [ + { + "name": "T", + "type": 0 + }, + { + "name": "E", + "type": 4 + } + ], + "path": [ + "Result" + ] + } + }, + { + "id": 6, + "type": { + "def": { + "primitive": "u8" + } + } + }, + { + "id": 7, + "type": { + "def": { + "composite": { + "fields": [ + { + "type": 8, + "typeName": "[u8; 32]" + } + ] + } + }, + "path": [ + "ink_primitives", + "types", + "AccountId" + ] + } + }, + { + "id": 8, + "type": { + "def": { + "array": { + "len": 32, + "type": 6 + } + } + } + }, + { + "id": 9, + "type": { + "def": { + "primitive": "u128" + } + } + }, + { + "id": 10, + "type": { + "def": { + "composite": { + "fields": [ + { + "type": 8, + "typeName": "[u8; 32]" + } + ] + } + }, + "path": [ + "ink_primitives", + "types", + "Hash" + ] + } + }, + { + "id": 11, + "type": { + "def": { + "primitive": "u64" + } + } + }, + { + "id": 12, + "type": { + "def": { + "primitive": "u32" + } + } + }, + { + "id": 13, + "type": { + "def": { + "variant": {} + }, + "path": [ + "ink_env", + "types", + "NoChainExtension" + ] + } + } + ], + "version": 5 +} \ No newline at end of file diff --git a/examples/assets/flipper-v5.wasm b/examples/assets/flipper-v5.wasm new file mode 100644 index 0000000..54f1aa1 Binary files /dev/null and b/examples/assets/flipper-v5.wasm differ diff --git a/examples/create_and_exec_contract.py b/examples/create_and_exec_contract.py index 9fc0dbb..6edc837 100644 --- a/examples/create_and_exec_contract.py +++ b/examples/create_and_exec_contract.py @@ -37,15 +37,15 @@ # Create contract instance from deterministic address contract = ContractInstance.create_from_address( contract_address=contract_address, - metadata_file=os.path.join(os.path.dirname(__file__), 'assets', 'flipper-v4.json'), + metadata_file=os.path.join(os.path.dirname(__file__), 'assets', 'flipper-v5.json'), substrate=substrate ) else: # Upload WASM code code = ContractCode.create_from_contract_files( - metadata_file=os.path.join(os.path.dirname(__file__), 'assets', 'flipper-v4.json'), - wasm_file=os.path.join(os.path.dirname(__file__), 'assets', 'flipper-v4.wasm'), + metadata_file=os.path.join(os.path.dirname(__file__), 'assets', 'flipper-v5.json'), + wasm_file=os.path.join(os.path.dirname(__file__), 'assets', 'flipper-v5.wasm'), substrate=substrate ) diff --git a/substrateinterface/contracts.py b/substrateinterface/contracts.py index 14945d4..2ca1ab9 100644 --- a/substrateinterface/contracts.py +++ b/substrateinterface/contracts.py @@ -90,7 +90,7 @@ def __convert_to_latest_metadata(self): elif 'version' in self.metadata_dict: self.metadata_version = int(self.metadata_dict['version']) - if self.metadata_version is None or self.metadata_version > 4: + if self.metadata_version is None or self.metadata_version > 5: raise ContractMetadataParseException("Unsupported metadata version") if 1 <= self.metadata_version <= 3: @@ -126,6 +126,13 @@ def replace_name_with_label(obj): for idx, c in enumerate(self.metadata_dict['spec']['constructors']): c["payable"] = True + # V4 -> V5: new event fields: module_path and signature_topic + if self.metadata_version <= 4: + for idx, c in enumerate(self.metadata_dict['spec']['events']): + c["module_path"] = None + c["signature_topic"] = None + + def __parse_metadata(self): self.__convert_to_latest_metadata() @@ -392,6 +399,13 @@ def get_event_data(self, event_id: int) -> dict: return self.metadata_dict['spec']['events'][event_id] + def get_event_id_by_topic(self, topic: str) -> Optional[int]: + for event_id, event in enumerate(self.metadata_dict['spec']['events']): + if topic == event['signature_topic']: + return event_id + + # raise ValueError(f'Contract event for topic "{topic}" not found') + class ContractEvent(ScaleType): @@ -485,9 +499,20 @@ def process_events(self): if self.substrate.implements_scaleinfo(): if event.value['module_id'] == 'Contracts' and event.value['event_id'] == 'ContractEmitted' and event.value['attributes']['contract'] == self.contract_address: + + contract_data = event['event'][1][1]['data'].value_object + + if self.contract_metadata.metadata_version >= 5: + # Find corresponding contract event by event topic + for topic in event.value['topics']: + event_id = self.contract_metadata.get_event_id_by_topic(topic) + if event_id is not None: + event_bytes = self.substrate.create_scale_object("U8").encode(event_id).data + contract_data = event_bytes + contract_data + # Create contract event contract_event_obj = ContractEvent( - data=ScaleBytes(event['event'][1][1]['data'].value_object), + data=ScaleBytes(contract_data), runtime_config=self.substrate.runtime_config, contract_metadata=self.contract_metadata ) diff --git a/test/fixtures/flipper-v5.json b/test/fixtures/flipper-v5.json new file mode 100644 index 0000000..73a10f1 --- /dev/null +++ b/test/fixtures/flipper-v5.json @@ -0,0 +1,463 @@ +{ + "source": { + "hash": "0x04208d5b3de1808ed1ccf83d08bdb5e3974bba7c4af8d5fc1ed6d6c725155c2e", + "language": "ink! 5.0.0", + "compiler": "rustc 1.81.0-nightly", + "build_info": { + "build_mode": "Debug", + "cargo_contract_version": "4.1.1", + "rust_toolchain": "nightly-aarch64-apple-darwin", + "wasm_opt_settings": { + "keep_debug_symbols": false, + "optimization_passes": "Z" + } + } + }, + "contract": { + "name": "flipper5", + "version": "0.1.0", + "authors": [ + "[your_name] <[your_email]>" + ] + }, + "image": null, + "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": 2 + }, + "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": 2 + }, + "selector": "0xed4b9d1b" + } + ], + "docs": [], + "environment": { + "accountId": { + "displayName": [ + "AccountId" + ], + "type": 7 + }, + "balance": { + "displayName": [ + "Balance" + ], + "type": 9 + }, + "blockNumber": { + "displayName": [ + "BlockNumber" + ], + "type": 12 + }, + "chainExtension": { + "displayName": [ + "ChainExtension" + ], + "type": 13 + }, + "hash": { + "displayName": [ + "Hash" + ], + "type": 10 + }, + "maxEventTopics": 4, + "staticBufferSize": 16384, + "timestamp": { + "displayName": [ + "Timestamp" + ], + "type": 11 + } + }, + "events": [ + { + "args": [ + { + "docs": [], + "indexed": true, + "label": "value", + "type": { + "displayName": [ + "bool" + ], + "type": 0 + } + } + ], + "docs": [], + "label": "Flipped", + "module_path": "flipper5::flipper5", + "signature_topic": "0x529cf346ddea0543633a1d91f021fa688fb7fe023ee1fb83ad031fe005673254" + }, + { + "args": [ + { + "docs": [], + "indexed": true, + "label": "test_value", + "type": { + "displayName": [ + "u8" + ], + "type": 6 + } + } + ], + "docs": [], + "label": "Test", + "module_path": "flipper5::flipper5", + "signature_topic": "0xc04204b5a8f12647ea7e92832f7608f4b5279fbcd2181333ff6a96906e5d555f" + } + ], + "lang_error": { + "displayName": [ + "ink", + "LangError" + ], + "type": 4 + }, + "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": 2 + }, + "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": 5 + }, + "selector": "0x2f865bd9" + } + ] + }, + "storage": { + "root": { + "layout": { + "struct": { + "fields": [ + { + "layout": { + "leaf": { + "key": "0x00000000", + "ty": 0 + } + }, + "name": "value" + } + ], + "name": "Flipper5" + } + }, + "root_key": "0x00000000", + "ty": 1 + } + }, + "types": [ + { + "id": 0, + "type": { + "def": { + "primitive": "bool" + } + } + }, + { + "id": 1, + "type": { + "def": { + "composite": { + "fields": [ + { + "name": "value", + "type": 0, + "typeName": ",>>::Type" + } + ] + } + }, + "path": [ + "flipper5", + "flipper5", + "Flipper5" + ] + } + }, + { + "id": 2, + "type": { + "def": { + "variant": { + "variants": [ + { + "fields": [ + { + "type": 3 + } + ], + "index": 0, + "name": "Ok" + }, + { + "fields": [ + { + "type": 4 + } + ], + "index": 1, + "name": "Err" + } + ] + } + }, + "params": [ + { + "name": "T", + "type": 3 + }, + { + "name": "E", + "type": 4 + } + ], + "path": [ + "Result" + ] + } + }, + { + "id": 3, + "type": { + "def": { + "tuple": [] + } + } + }, + { + "id": 4, + "type": { + "def": { + "variant": { + "variants": [ + { + "index": 1, + "name": "CouldNotReadInput" + } + ] + } + }, + "path": [ + "ink_primitives", + "LangError" + ] + } + }, + { + "id": 5, + "type": { + "def": { + "variant": { + "variants": [ + { + "fields": [ + { + "type": 0 + } + ], + "index": 0, + "name": "Ok" + }, + { + "fields": [ + { + "type": 4 + } + ], + "index": 1, + "name": "Err" + } + ] + } + }, + "params": [ + { + "name": "T", + "type": 0 + }, + { + "name": "E", + "type": 4 + } + ], + "path": [ + "Result" + ] + } + }, + { + "id": 6, + "type": { + "def": { + "primitive": "u8" + } + } + }, + { + "id": 7, + "type": { + "def": { + "composite": { + "fields": [ + { + "type": 8, + "typeName": "[u8; 32]" + } + ] + } + }, + "path": [ + "ink_primitives", + "types", + "AccountId" + ] + } + }, + { + "id": 8, + "type": { + "def": { + "array": { + "len": 32, + "type": 6 + } + } + } + }, + { + "id": 9, + "type": { + "def": { + "primitive": "u128" + } + } + }, + { + "id": 10, + "type": { + "def": { + "composite": { + "fields": [ + { + "type": 8, + "typeName": "[u8; 32]" + } + ] + } + }, + "path": [ + "ink_primitives", + "types", + "Hash" + ] + } + }, + { + "id": 11, + "type": { + "def": { + "primitive": "u64" + } + } + }, + { + "id": 12, + "type": { + "def": { + "primitive": "u32" + } + } + }, + { + "id": 13, + "type": { + "def": { + "variant": {} + }, + "path": [ + "ink_env", + "types", + "NoChainExtension" + ] + } + } + ], + "version": 5 +} \ No newline at end of file diff --git a/test/test_contracts.py b/test/test_contracts.py index 1075696..432a799 100644 --- a/test/test_contracts.py +++ b/test/test_contracts.py @@ -409,5 +409,59 @@ def setUp(self) -> None: ) +class FlipperInstanceV5TestCase(FlipperInstanceTestCase): + + @classmethod + def setUpClass(cls): + + class MockedSubstrateInterface(SubstrateInterface): + + def rpc_request(self, method, params, result_handler=None): + if method == 'state_call': + return { + 'jsonrpc': '2.0', + 'result': '0x1a987a663dbd8e5cf16786bc0100010000000000000000000000000000000000000000000008000000', + 'id': 16 + } + if method == 'contracts_call': + return { + 'jsonrpc': '2.0', + 'result': { + 'gasConsumed': 7419127834, + 'gasRequired': 74999922688, + 'storageDeposit': {'charge': '0x0'}, + 'debugMessage': '', + 'result': {'Ok': {'flags': 0, 'data': '0x0000'}} + }, + 'id': self.request_id} + + return super().rpc_request(method, params, result_handler) + + cls.substrate = MockedSubstrateInterface( + url=settings.KUSAMA_NODE_URL, type_registry_preset='canvas', type_registry={'types': {"ContractExecResult": "ContractExecResultTo269"}} + ) + + cls.keypair = Keypair.create_from_uri('//Alice') + + def setUp(self) -> None: + self.contract = ContractInstance.create_from_address( + contract_address="5DaohteAvvR9PZEhynqWvbFT8HEaHNuiiPTZV61VEUHnqsfU", + metadata_file=os.path.join(os.path.dirname(__file__), 'fixtures', 'flipper-v5.json'), + substrate=self.substrate + ) + + def test_instance_read(self): + + result = self.contract.read(self.keypair, 'get') + + self.assertEqual({'Ok': False}, result.contract_result_data.value) + + def test_instance_read_at_not_best_block(self): + parent_hash = self.substrate.get_block_header()['header']['parentHash'] + result = self.contract.read(self.keypair, 'get', block_hash=parent_hash) + + self.assertEqual({'Ok': False}, result.contract_result_data.value) + + if __name__ == '__main__': unittest.main()