Skip to content

Commit 50eb04f

Browse files
authored
Upgrade contract interface to support WeightV2 (#337)
* * Use WeightV2 as gas_limit * Removed legacy contract code * Updated contract example * Fixed unit tests * Refactored deploy function Updated scalecodec
1 parent 1f558e6 commit 50eb04f

File tree

6 files changed

+75
-225
lines changed

6 files changed

+75
-225
lines changed

examples/create_and_exec_contract.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,7 @@
2525

2626
try:
2727
substrate = SubstrateInterface(
28-
url="ws://127.0.0.1:9944",
29-
type_registry_preset='canvas'
28+
url="ws://127.0.0.1:9944"
3029
)
3130

3231
keypair = Keypair.create_from_uri('//Alice')
@@ -58,10 +57,10 @@
5857
print('Deploy contract...')
5958
contract = code.deploy(
6059
keypair=keypair,
61-
endowment=0,
62-
gas_limit=1000000000000,
6360
constructor="new",
6461
args={'init_value': True},
62+
value=0,
63+
gas_limit={'ref_time': 25990000000, 'proof_size': 11990383647911208550},
6564
upload_code=True
6665
)
6766

examples/extensions.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@
1111
substrate.register_extension(SubstrateNodeSearchExtension(max_block_range=100))
1212

1313
# Search for block number corresponding a specific datetime
14-
block_datetime = datetime(2000, 7, 12, 0, 0, 0)
14+
block_datetime = datetime(2022, 1, 1, 0, 0, 0)
1515
block_number = substrate.extensions.search_block_number(block_datetime=block_datetime)
1616
print(f'Block number for {block_datetime}: #{block_number}')
1717

18+
# account_info = substrate.runtime.
19+
# exit()
1820

1921
# Returns all `Balances.Transfer` events from the last 30 blocks
2022
events = substrate.extensions.filter_events(pallet_name="Balances", event_name="Transfer", block_start=-30)

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ eth_utils>=1.3.0,<3
1111
pycryptodome>=3.11.0,<4
1212
PyNaCl>=1.0.1,<2
1313

14-
scalecodec>=1.2.2,<1.3
14+
scalecodec>=1.2.3,<1.3
1515
py-sr25519-bindings>=0.2.0,<1
1616
py-ed25519-zebra-bindings>=1.0,<2
1717
py-bip39-bindings>=0.1.9,<1

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@
188188
'eth_utils>=1.3.0,<3',
189189
'pycryptodome>=3.11.0,<4',
190190
'PyNaCl>=1.0.1,<2',
191-
'scalecodec>=1.2.2,<1.3',
191+
'scalecodec>=1.2.3,<1.3',
192192
'py-sr25519-bindings>=0.2.0,<1',
193193
'py-ed25519-zebra-bindings>=1.0,<2',
194194
'py-bip39-bindings>=0.1.9,<1'

substrateinterface/contracts.py

Lines changed: 46 additions & 147 deletions
Original file line numberDiff line numberDiff line change
@@ -621,7 +621,7 @@ def upload_wasm(self, keypair: Keypair, storage_deposit_limit: int = None) -> Ex
621621

622622
return self.substrate.submit_extrinsic(extrinsic, wait_for_inclusion=True)
623623

624-
def deploy(self, keypair: Keypair, endowment: int, gas_limit: int, constructor: str, args: dict = None,
624+
def deploy(self, keypair: Keypair, constructor: str, args: dict = None, value: int = 0, gas_limit: dict = None,
625625
deployment_salt: str = None, upload_code: bool = False, storage_deposit_limit: int = None
626626
) -> "ContractInstance":
627627
"""
@@ -631,10 +631,10 @@ def deploy(self, keypair: Keypair, endowment: int, gas_limit: int, constructor:
631631
Parameters
632632
----------
633633
keypair
634-
endowment: Initial deposit for the newly created contract address
635-
gas_limit:
636634
constructor: name of the constructor to use, provided in the metadata
637635
args: arguments for the constructor
636+
value: Value sent to created contract address
637+
gas_limit: Gas limit as WeightV2 type. Will default to {'ref_time': 25990000000, 'proof_size': 11990383647911208550}.
638638
deployment_salt: optional string or hex-string that acts as a salt for this deployment
639639
upload_code: When True the WASM blob itself will be uploaded with the deploy, False if the WASM is already present on-chain
640640
storage_deposit_limit: The maximum amount of balance that can be charged to pay for the storage consumed.
@@ -647,62 +647,33 @@ def deploy(self, keypair: Keypair, endowment: int, gas_limit: int, constructor:
647647
# Lookup constructor
648648
data = self.metadata.generate_constructor_data(name=constructor, args=args)
649649

650-
# Check metadata for available call functions
651-
call_function = self.substrate.get_metadata_call_function('Contracts', 'instantiate_with_code')
652-
if call_function is not None:
653-
654-
# Check gas_limit weight format
655-
param_info = call_function.get_param_info()
656-
if type(param_info['gas_limit']) is dict:
657-
gas_limit = {'ref_time': gas_limit, 'proof_size': 0}
658-
650+
if gas_limit is None:
651+
gas_limit = {'ref_time': 25990000000, 'proof_size': 11990383647911208550}
659652

660653
if upload_code is True:
661654

662-
if call_function is not None:
663-
664-
if not self.wasm_bytes:
665-
raise ValueError("No WASM bytes to upload")
666-
667-
call = self.substrate.compose_call(
668-
call_module='Contracts',
669-
call_function='instantiate_with_code',
670-
call_params={
671-
'endowment': endowment, # deprecated
672-
'value': endowment,
673-
'gas_limit': gas_limit,
674-
'storage_deposit_limit': storage_deposit_limit,
675-
'code': '0x{}'.format(self.wasm_bytes.hex()),
676-
'data': data.to_hex(),
677-
'salt': deployment_salt or ''
678-
}
679-
)
680-
else:
681-
# Legacy mode: put code in separate call
682-
683-
self.upload_wasm(keypair)
684-
685-
call = self.substrate.compose_call(
686-
call_module='Contracts',
687-
call_function='instantiate',
688-
call_params={
689-
'endowment': endowment, # deprecated
690-
'value': endowment,
691-
'gas_limit': gas_limit,
692-
'storage_deposit_limit': storage_deposit_limit,
693-
'code_hash': f'0x{self.code_hash.hex()}',
694-
'data': data.to_hex(),
695-
'salt': deployment_salt or ''
696-
}
697-
)
655+
if not self.wasm_bytes:
656+
raise ValueError("No WASM bytes to upload")
657+
658+
call = self.substrate.compose_call(
659+
call_module='Contracts',
660+
call_function='instantiate_with_code',
661+
call_params={
662+
'value': value,
663+
'gas_limit': gas_limit,
664+
'storage_deposit_limit': storage_deposit_limit,
665+
'code': '0x{}'.format(self.wasm_bytes.hex()),
666+
'data': data.to_hex(),
667+
'salt': deployment_salt or ''
668+
}
669+
)
698670
else:
699671

700672
call = self.substrate.compose_call(
701673
call_module='Contracts',
702674
call_function='instantiate',
703675
call_params={
704-
'endowment': endowment, # deprecated
705-
'value': endowment,
676+
'value': value,
706677
'gas_limit': gas_limit,
707678
'storage_deposit_limit': storage_deposit_limit,
708679
'code_hash': f'0x{self.code_hash.hex()}',
@@ -782,7 +753,7 @@ def read(self, keypair: Keypair, method: str, args: dict = None,
782753
method: name of message to execute
783754
args: arguments of message in {'name': value} format
784755
value: value to send when executing the message
785-
gas_limit:
756+
gas_limit: dict repesentation of `WeightV2` type
786757
787758
Returns
788759
-------
@@ -791,105 +762,34 @@ def read(self, keypair: Keypair, method: str, args: dict = None,
791762

792763
input_data = self.metadata.generate_message_data(name=method, args=args)
793764

794-
if self.substrate.supports_rpc_method('state_call'):
795-
call_result = self.substrate.runtime_call("ContractsApi", "call", {
796-
'dest': self.contract_address,
797-
'gas_limit': gas_limit,
798-
'input_data': input_data.to_hex(),
799-
'origin': keypair.ss58_address,
800-
'value': value,
801-
'storage_deposit_limit': None
802-
})
803-
if 'Error' in call_result['result']:
804-
raise ContractReadFailedException(call_result.value['result']['Error'])
805-
806-
if 'Ok' in call_result['result']:
807-
808-
try:
809-
return_type_string = self.metadata.get_return_type_string_for_message(method)
810-
result_scale_obj = self.substrate.create_scale_object(return_type_string)
811-
result_scale_obj.decode(ScaleBytes(call_result['result'][1]['data'].value_object))
812-
call_result.value_object['result'].value_object[1].value_object['data'] = result_scale_obj
813-
call_result.value['result']['Ok']['data'] = result_scale_obj.value
814-
815-
except NotImplementedError:
816-
pass
817-
818-
return call_result
819-
820-
else:
821-
# Deprecated RPC call
822-
response = self.substrate.rpc_request(method='contracts_call', params=[{
823-
'dest': self.contract_address,
824-
'gasLimit': gas_limit,
825-
'inputData': input_data.to_hex(),
826-
'origin': keypair.ss58_address,
827-
'value': value
828-
}])
829-
830-
if 'result' in response:
831-
832-
self.substrate.init_runtime()
833-
765+
# Execute runtime call in ContractsApi
766+
call_result = self.substrate.runtime_call("ContractsApi", "call", {
767+
'dest': self.contract_address,
768+
'gas_limit': gas_limit,
769+
'input_data': input_data.to_hex(),
770+
'origin': keypair.ss58_address,
771+
'value': value,
772+
'storage_deposit_limit': None
773+
})
774+
if 'Error' in call_result['result']:
775+
raise ContractReadFailedException(call_result.value['result']['Error'])
776+
777+
if 'Ok' in call_result['result']:
778+
779+
try:
834780
return_type_string = self.metadata.get_return_type_string_for_message(method)
781+
result_scale_obj = self.substrate.create_scale_object(return_type_string)
782+
result_scale_obj.decode(ScaleBytes(call_result['result'][1]['data'].value_object))
783+
call_result.value_object['result'].value_object[1].value_object['data'] = result_scale_obj
784+
call_result.value['result']['Ok']['data'] = result_scale_obj.value
835785

836-
# Wrap the result in a ContractExecResult Enum because the exec will result in the same
837-
ContractExecResult = self.substrate.runtime_config.get_decoder_class('ContractExecResult')
838-
839-
contract_exec_result = ContractExecResult(contract_result_scale_type=return_type_string)
840-
841-
if 'result' in response['result']:
842-
843-
contract_exec_result.gas_consumed = response['result']['gasConsumed']
844-
contract_exec_result.gas_required = response['result']['gasRequired']
845-
846-
if 'Ok' in response['result']['result']:
847-
848-
contract_exec_result.flags = response['result']['result']['Ok']['flags']
849-
850-
try:
851-
852-
result_scale_obj = self.substrate.decode_scale(
853-
type_string=return_type_string,
854-
scale_bytes=ScaleBytes(response['result']['result']['Ok']['data']),
855-
return_scale_obj=True
856-
)
786+
except NotImplementedError:
787+
pass
857788

858-
response['result']['result']['Ok']['data'] = result_scale_obj.value
859-
contract_exec_result.contract_result_data = result_scale_obj
860-
contract_exec_result.value_object = result_scale_obj
861-
862-
except NotImplementedError:
863-
pass
864-
865-
# Backwards compatibility
866-
elif 'success' in response['result']:
867-
868-
contract_exec_result.gas_consumed = response['result']['success']['gas_consumed']
869-
contract_exec_result.flags = response['result']['success']['flags']
870-
871-
try:
872-
873-
result_scale_obj = self.substrate.decode_scale(
874-
type_string=return_type_string,
875-
scale_bytes=ScaleBytes(response['result']['success']['data']),
876-
return_scale_obj=True
877-
)
878-
879-
response['result']['success']['data'] = result_scale_obj.value
880-
contract_exec_result.contract_result_data = result_scale_obj
881-
882-
except NotImplementedError:
883-
pass
884-
885-
contract_exec_result.value = response['result']
886-
887-
return contract_exec_result
888-
889-
raise ContractReadFailedException(response)
789+
return call_result
890790

891791
def exec(self, keypair: Keypair, method: str, args: dict = None,
892-
value: int = 0, gas_limit: Optional[int] = None, storage_deposit_limit: int = None
792+
value: int = 0, gas_limit: Optional[dict] = None, storage_deposit_limit: int = None
893793
) -> ContractExecutionReceipt:
894794
"""
895795
Executes provided message by creating and submitting an extrinsic. To get a gas prediction or perform a
@@ -901,7 +801,7 @@ def exec(self, keypair: Keypair, method: str, args: dict = None,
901801
method: name of message to execute
902802
args: arguments of message in {'name': value} format
903803
value: value to send when executing the message
904-
gas_limit: When left to None the gas limit will be calculated with a read()
804+
gas_limit: dict repesentation of `WeightV2` type. When omited the gas limit will be calculated with a `read()`
905805
storage_deposit_limit: The maximum amount of balance that can be charged to pay for the storage consumed
906806
907807
Returns
@@ -932,4 +832,3 @@ def exec(self, keypair: Keypair, method: str, args: dict = None,
932832
receipt = self.substrate.submit_extrinsic(extrinsic, wait_for_inclusion=True)
933833

934834
return ContractExecutionReceipt.create_from_extrinsic_receipt(receipt, self.metadata)
935-

0 commit comments

Comments
 (0)