From e717f0d504afd34018c2b32e004eee72e5f28620 Mon Sep 17 00:00:00 2001 From: ixje Date: Mon, 14 Oct 2024 13:37:32 +0200 Subject: [PATCH] api, tests: streamline invoke return types (#319) --- examples/contract-deploy-update-destroy.py | 8 +-- examples/nep-11-airdrop.py | 7 +- examples/nep17-airdrop.py | 8 +-- examples/nep17-transfer.py | 2 +- examples/vote.py | 3 +- neo3/api/wrappers.py | 77 +++++++++++++++++----- 6 files changed, 77 insertions(+), 28 deletions(-) diff --git a/examples/contract-deploy-update-destroy.py b/examples/contract-deploy-update-destroy.py index b7402ae7..d839effe 100644 --- a/examples/contract-deploy-update-destroy.py +++ b/examples/contract-deploy-update-destroy.py @@ -38,8 +38,8 @@ async def main(neoxp: shared.NeoExpress): contract = GenericContract(contract_hash) print("Calling `add` with input 1, result is: ", end="") # using test_invoke here because we don't really care about the result being persisted to the chain - result = await facade.test_invoke(contract.call_function("add", [1])) - print(unwrap.as_int(result)) + receipt = await facade.test_invoke(contract.call_function("add", [1])) + print(unwrap.as_int(receipt.result)) print("Updating contract with version 2...", end="") nef_v2 = nef.NEF.from_file(files_path + "contract_v2.nef") @@ -52,8 +52,8 @@ async def main(neoxp: shared.NeoExpress): print("Calling `add` with input 1, result is: ", end="") # Using test_invoke here because we don't really care about the result being persisted to the chain - result = await facade.test_invoke(contract.call_function("add", [1])) - print(unwrap.as_int(result)) + receipt = await facade.test_invoke(contract.call_function("add", [1])) + print(unwrap.as_int(receipt.result)) print("Destroying contract...", end="") # destroy also doesn't give any return value. So if it doesn't fail then it means success diff --git a/examples/nep-11-airdrop.py b/examples/nep-11-airdrop.py index d82cabe4..3e5d022e 100644 --- a/examples/nep-11-airdrop.py +++ b/examples/nep-11-airdrop.py @@ -21,8 +21,8 @@ async def example_airdrop(neoxp: shared.NeoExpress): # Wrap the NFT contract ntf = NEP11NonDivisibleContract(shared.nep11_token_hash) - balance = len(await facade.test_invoke(ntf.token_ids_owned_by(account.address))) - print(f"Current NFT balance: {balance}") + receipt = await facade.test_invoke(ntf.token_ids_owned_by(account.address)) + print(f"Current NFT balance: {len(receipt.result)}") # First we have to mint the NFTs to our own wallet # We do this by sending 10 GAS to the contract. We do this in 2 separate transactions because the NFT is @@ -47,7 +47,8 @@ async def example_airdrop(neoxp: shared.NeoExpress): ) ) print(receipt.result) - token_ids = await facade.test_invoke(ntf.token_ids_owned_by(account.address)) + receipt = await facade.test_invoke(ntf.token_ids_owned_by(account.address)) + token_ids = receipt.result print(f"New NFT token balance: {len(token_ids)}, ids: {token_ids}") # Now let's airdrop the NFTs diff --git a/examples/nep17-airdrop.py b/examples/nep17-airdrop.py index 654a65bb..5dbf65ea 100644 --- a/examples/nep17-airdrop.py +++ b/examples/nep17-airdrop.py @@ -23,8 +23,8 @@ async def example_airdrop(neoxp: shared.NeoExpress): # Use the generic NEP17 class to wrap the token token = NEP17Contract(shared.coz_token_hash) - balance = await facade.test_invoke(token.balance_of(account.address)) - print(f"Current COZ token balance: {balance}") + receipt = await facade.test_invoke(token.balance_of(account.address)) + print(f"Current COZ token balance: {receipt.result}") # First we have to mint the tokens to our own wallet # We do this by sending NEO to the contract @@ -46,8 +46,8 @@ async def example_airdrop(neoxp: shared.NeoExpress): print(receipt.result) - balance = await facade.test_invoke(token.balance_of(account.address)) - print(f"New COZ token balance: {balance}") + receipt = await facade.test_invoke(token.balance_of(account.address)) + print(f"New COZ token balance: {receipt.result}") # Now let's airdrop the tokens destination_addresses = [ diff --git a/examples/nep17-transfer.py b/examples/nep17-transfer.py index c725894f..86d850d0 100644 --- a/examples/nep17-transfer.py +++ b/examples/nep17-transfer.py @@ -42,7 +42,7 @@ async def example_transfer_other(neoxp: shared.NeoExpress): facade = ChainFacade(rpc_host=neoxp.rpc_host) facade.add_signer( sign_insecure_with_account(account, password="123"), - Signer(account.script_hash), # default scope is CALLED_BY_ENTRY + Signer(account.script_hash), # default scope is te/CALLED_BY_ENTRY ) source = account.address diff --git a/examples/vote.py b/examples/vote.py index de44e253..064946a8 100644 --- a/examples/vote.py +++ b/examples/vote.py @@ -22,7 +22,8 @@ async def example_vote(neoxp: shared.NeoExpress): # Dedicated Neo native contract wrapper neo = NeoToken() # get a list of candidates that can be voted on - candidates = await facade.test_invoke(neo.candidates_registered()) + receipt = await facade.test_invoke(neo.candidates_registered()) + candidates = receipt.result # the example chain only has 1 candidate, use that candidate_pk = candidates[0].public_key diff --git a/neo3/api/wrappers.py b/neo3/api/wrappers.py index 2dd7b981..88e0d5a9 100644 --- a/neo3/api/wrappers.py +++ b/neo3/api/wrappers.py @@ -159,7 +159,7 @@ async def test_invoke( f: ContractMethodResult[ReturnType], *, signers: Optional[Sequence[verification.Signer]] = None, - ) -> ReturnType: + ) -> InvokeReceipt[ReturnType]: """ Call a contract method in read-only mode. This does not persist any state on the actual chain and therefore does not require signing or paying GAS. @@ -180,9 +180,9 @@ async def test_invoke_multi( f: list[ContractMethodResult], *, signers: Optional[Sequence[verification.Signer]] = None, - ) -> Sequence: + ) -> InvokeReceipt[Sequence]: """ - Call all contract methods in one go (concurrently) and return the list of results. + Call all contract methods in one go (concatenated in 1 script) and return the list of results. Args: f: list of functions to call. @@ -193,8 +193,35 @@ async def test_invoke_multi( """ if signers is None: signers = self.signers - return await asyncio.gather( - *map(lambda c: self.test_invoke(c, signers=signers), f) + script = bytearray() + for call in f: # type: ContractMethodResult + script.extend(call.script) + + wrapped: ContractMethodResult[None] = ContractMethodResult(script) + receipt = await self.test_invoke_raw(wrapped, signers=signers) + + results = [] + stack_offset = 0 + for call in f: + res_cpy = deepcopy(receipt.result) + # adjust the stack so that it becomes transparent for the post-processing functions. + res_cpy.stack = res_cpy.stack[ + stack_offset : stack_offset + call.return_count + ] + if call.execution_processor is None: + results.append(res_cpy) + else: + results.append(call.execution_processor(res_cpy, 0)) + stack_offset += call.return_count + return InvokeReceipt[Sequence]( + receipt.tx_hash, + receipt.included_in_block, + receipt.confirmations, + receipt.gas_consumed, + receipt.state, + receipt.exception, + receipt.notifications, + results, ) async def test_invoke_raw( @@ -202,7 +229,7 @@ async def test_invoke_raw( f: ContractMethodResult[ReturnType], *, signers: Optional[Sequence[verification.Signer]] = None, - ) -> noderpc.ExecutionResult: + ) -> InvokeReceipt[noderpc.ExecutionResult]: """ Call a contract method in read-only mode. This does not persist any state on the actual chain and therefore does not require signing or paying GAS. @@ -217,7 +244,8 @@ async def test_invoke_raw( """ if signers is None: signers = self.signers - return await self._test_invoke(f, signers=signers, return_raw=True) + res = await self._test_invoke(f, signers=signers, return_raw=True) + return cast(InvokeReceipt[noderpc.ExecutionResult], res) async def _test_invoke( self, @@ -225,7 +253,7 @@ async def _test_invoke( *, signers: Optional[Sequence[verification.Signer]] = None, return_raw: Optional[bool] = False, - ): + ) -> InvokeReceipt[ReturnType]: """ Args: f: @@ -234,9 +262,21 @@ async def _test_invoke( """ async with noderpc.NeoRpcClient(self.rpc_host) as client: res = await client.invoke_script(f.script, signers) + if f.execution_processor is None or return_raw or res.state != "HALT": - return res - return f.execution_processor(res, 0) + result = res + else: + result = f.execution_processor(res, 0) + return InvokeReceipt[ReturnType]( + types.UInt256.zero(), + -1, + -1, + res.gas_consumed, + res.state, + res.exception, + res.notifications, + result, + ) async def invoke( self, @@ -448,7 +488,7 @@ async def invoke_multi( append_network_fee: int = 0, append_system_fee: int = 0, _post_processing: bool = True, - ) -> Sequence: + ) -> InvokeReceipt[Sequence]: """ Call all contract methods (concatenated) in one go and persist results on the chain. Costs GAS. Waits for tx to be included in a block. Automatically post processes the execution results according to the @@ -499,7 +539,16 @@ async def invoke_multi( else: results.append(call.execution_processor(res_cpy, 0)) stack_offset += call.return_count - return results + return InvokeReceipt[Sequence]( + receipt.tx_hash, + receipt.included_in_block, + receipt.confirmations, + receipt.execution.gas_consumed, + receipt.execution.state, + receipt.execution.exception, + receipt.execution.notifications, + results, + ) async def invoke_multi_fast( self, @@ -560,7 +609,7 @@ async def invoke_multi_raw( system_fee: int = 0, append_network_fee: int = 0, append_system_fee: int = 0, - ) -> Sequence: + ) -> InvokeReceipt[Sequence]: """ Call all contract methods (concatenated) in one go and persist results on the chain. Costs GAS. Do not wait for tx to be included in a block. Do not post process the execution results according to @@ -1288,7 +1337,6 @@ def process(res: noderpc.ExecutionResult, _: int = 0) -> list[types.UInt160]: return ContractMethodResult(sb.to_array(), process) - def total_owned_by( self, owner: types.UInt160 | NeoAddress, token_id: bytes ) -> ContractMethodResult[int]: @@ -1304,7 +1352,6 @@ def total_owned_by( ) return ContractMethodResult(sb.to_array(), unwrap.as_int) - def total_owned_by_friendly( self, owner: types.UInt160 | NeoAddress, token_id: bytes ) -> ContractMethodResult[float]: