Skip to content

Commit

Permalink
api, tests: streamline invoke return types (#319)
Browse files Browse the repository at this point in the history
  • Loading branch information
ixje authored Oct 14, 2024
1 parent e020274 commit e717f0d
Show file tree
Hide file tree
Showing 6 changed files with 77 additions and 28 deletions.
8 changes: 4 additions & 4 deletions examples/contract-deploy-update-destroy.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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
Expand Down
7 changes: 4 additions & 3 deletions examples/nep-11-airdrop.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
8 changes: 4 additions & 4 deletions examples/nep17-airdrop.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 = [
Expand Down
2 changes: 1 addition & 1 deletion examples/nep17-transfer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion examples/vote.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
77 changes: 62 additions & 15 deletions neo3/api/wrappers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
Expand All @@ -193,16 +193,43 @@ 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(
self,
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.
Expand All @@ -217,15 +244,16 @@ 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,
f: ContractMethodResult[ReturnType],
*,
signers: Optional[Sequence[verification.Signer]] = None,
return_raw: Optional[bool] = False,
):
) -> InvokeReceipt[ReturnType]:
"""
Args:
f:
Expand All @@ -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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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]:
Expand All @@ -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]:
Expand Down

0 comments on commit e717f0d

Please sign in to comment.