Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement oracle commands from Neo-Express to the NeoTestRunner interface #1097

Merged
merged 2 commits into from
Jul 18, 2023

Conversation

luc10921
Copy link
Contributor

Summary or solution description
Implemented the neoxp oracle enable and neoxp oracle response commands. Added new tests with responses.

How to Reproduce

from typing import Any, List
from boa3.builtin.compile_time import public
from boa3.builtin.interop import storage
from boa3.builtin.interop.oracle import OracleResponseCode
from boa3.builtin.interop.stdlib import serialize
from boa3.builtin.nativecontract.oracle import Oracle
from boa3.builtin.type import helper as type_helper
@public
def oracle_call(url: str, request_filter: str, callback: str, user_data: Any, gas_for_response: int) -> bool:
Oracle.request(url, request_filter, callback, user_data, gas_for_response)
return True
@public
def callback_method(requested_url: str, user_data: bytes, code: OracleResponseCode, request_result: bytes):
storage.put(b'pUrl', requested_url)
storage.put(b'pUser', serialize(user_data))
storage.put(b'pCode', type_helper.to_bytes(code))
storage.put(b'pRes', request_result)
@public
def get_storage() -> List[Any]:
a = storage.get(b'pUrl')
b = storage.get(b'pUser')
c = storage.get(b'pCode')
d = storage.get(b'pRes')
return [a, b, c, d]

Tests

def test_oracle_response(self):
path, _ = self.get_deploy_file_paths('OracleRequestCall.py')
runner = NeoTestRunner(runner_id=self.method_name())
invokes = []
expected_results = []
test_url = 'https://unittest.fake.url/api/0/'
callback = 'callback_method'
user_data = 'Any Data Here'
gas_for_response = 1 * 10 ** 8
from boa3_test.test_drive import neoxp
OWNER = neoxp.utils.get_account_by_name('owner')
genesis = neoxp.utils.get_account_by_name('genesis')
runner.oracle_enable(genesis)
runner.add_gas(OWNER.address, 1000 * 10 ** 8)
invokes.append(runner.call_contract(path, 'oracle_call', test_url, None, callback, user_data, gas_for_response))
expected_results.append(True)
invokes.append(runner.call_contract(path, 'get_storage'))
expected_results.append(['', '', '', ''])
runner.execute(account=OWNER, add_invokes_to_batch=True)
self.assertEqual(VMState.HALT, runner.vm_state, msg=runner.error)
runner.update_contracts(export_checkpoint=True)
path_json = path.replace('OracleRequestCall.nef', 'OracleResponse.json')
response_tx_ids = runner.oracle_response(test_url, path_json)
f = open(path_json)
import json
json_data = json.load(f)
f.close()
storage = runner.call_contract(path, 'get_storage')
from boa3_test.tests.test_classes.transactionattribute.oracleresponse import OracleResponseCode
runner.execute()
self.assertEqual(VMState.HALT, runner.vm_state, msg=runner.error)
for x in range(len(invokes)):
self.assertEqual(expected_results[x], invokes[x].result)
from boa3.internal.neo.vm.type.StackItem import StackItemType
self.assertEqual(test_url, storage.result[0])
self.assertEqual(f"{(StackItemType.ByteString + len(user_data).to_bytes(1,'little')).decode()}{user_data}",
storage.result[1])
self.assertEqual(OracleResponseCode.Success.to_bytes(1, 'little').decode(), storage.result[2])
self.assertEqual(json_data, json.loads(storage.result[3]))
self.assertEqual(1, len(response_tx_ids))
response_tx = runner.get_transaction(response_tx_ids[0])
self.assertIsNotNone(response_tx)
self.assertTrue(hasattr(response_tx, 'attributes'))
response_tx_attr = response_tx.attributes[0].to_json()
self.assertEqual('OracleResponse', response_tx_attr['type'])
self.assertEqual(0, response_tx_attr['id'])
self.assertEqual('Success', response_tx_attr['code'])
self.assertEqual(json_data, response_tx_attr['result'])
def test_oracle_response_filter(self):
path, _ = self.get_deploy_file_paths('OracleRequestCall.py')
runner = NeoTestRunner(runner_id=self.method_name())
invokes = []
expected_results = []
test_url = 'https://unittest.fake.url/api/0/'
callback = 'callback_method'
user_data = 'Any Data Here'
gas_for_response = 1 * 10 ** 8
from boa3_test.test_drive import neoxp
OWNER = neoxp.utils.get_account_by_name('owner')
runner.add_gas(OWNER.address, 1000 * 10 ** 8)
genesis = neoxp.utils.get_account_by_name('genesis')
runner.oracle_enable(genesis)
invokes.append(runner.call_contract(path, 'oracle_call', test_url, "$..book[-2:]", callback, user_data, gas_for_response))
expected_results.append(True)
invokes.append(runner.call_contract(path, 'oracle_call', test_url, "$.store.book[*].author", callback, user_data, gas_for_response))
expected_results.append(True)
invokes.append(runner.call_contract(path, 'oracle_call', test_url, "$.store.*", callback, user_data, gas_for_response))
expected_results.append(True)
runner.execute(account=OWNER, add_invokes_to_batch=True)
self.assertEqual(VMState.HALT, runner.vm_state, msg=runner.error)
runner.update_contracts(export_checkpoint=True)
path_json = path.replace('OracleRequestCall.nef', 'OracleResponse.json')
response_tx_ids = runner.oracle_response(test_url, path_json)
f = open(path_json)
import json
json_data = json.load(f)
f.close()
storage = runner.call_contract(path, 'get_storage')
runner.execute()
self.assertEqual(VMState.HALT, runner.vm_state, msg=runner.error)
for x in range(len(invokes)):
self.assertEqual(expected_results[x], invokes[x].result)
self.assertNotEqual(['', '', '', ''], storage)
self.assertEqual(3, len(response_tx_ids))
# Oracle can filter Json with JsonPath,
# Pythons json module doesn't use JsonPath, so it needs to be done manually
response_tx_attributes = [runner.get_transaction(tx_id).attributes[0].to_json() for tx_id in response_tx_ids]
books_inside_json = []
_deep_scan(json_data, 'book', books_inside_json)
# filter used was "$..book[-2:]"
last_2_books_inside_book = [book
for book_list in books_inside_json
for index, book in enumerate(book_list)
if index >= len(book_list) - 2]
self.assertEqual(last_2_books_inside_book, response_tx_attributes[0]['result'])
# filter used was "$.store.book[*].author"
authors_of_books_in_store = [book['author'] for book in json_data['store']['book']]
self.assertEqual(authors_of_books_in_store, response_tx_attributes[1]['result'])
# filter used was "$.store.*"
everything_inside_store = [json_data['store'][key] for key in json_data['store']]
self.assertEqual(everything_inside_store, response_tx_attributes[2]['result'])
def test_oracle_request_invalid_gas(self):
path, _ = self.get_deploy_file_paths('OracleRequestCall.py')
runner = NeoTestRunner(runner_id=self.method_name())
test_url = 'https://unittest.fake.url/api/0/'
callback = 'callback_method'
user_data = 'Any Data Here'
filter = "$.store.*"
gas_for_response = 9999999 # GAS can not be lower than 0.1 GAS
from boa3_test.test_drive import neoxp
genesis = neoxp.utils.get_account_by_name('genesis')
runner.oracle_enable(genesis)
runner.call_contract(path, 'oracle_call', test_url, filter, callback, user_data, gas_for_response)
runner.execute()
self.assertEqual(VMState.FAULT, runner.vm_state, msg=runner.cli_log)
self.assertRegex(runner.error, self.VALUE_DOES_NOT_FALL_WITHIN_EXPECTED_RANGE_MSG)
def test_oracle_request_invalid_callback(self):
path, _ = self.get_deploy_file_paths('OracleRequestCall.py')
runner = NeoTestRunner(runner_id=self.method_name())
test_url = 'https://unittest.fake.url/api/0/'
user_data = 'Any Data Here'
filter = "$.store.*"
gas_for_response = 1 * 10 ** 8
from boa3_test.test_drive import neoxp
genesis = neoxp.utils.get_account_by_name('genesis')
runner.oracle_enable(genesis)
callback = '_private_method' # method can not start with '_' (underscore)
runner.call_contract(path, 'oracle_call', test_url, filter, callback, user_data, gas_for_response)
runner.execute()
self.assertEqual(VMState.FAULT, runner.vm_state, msg=runner.cli_log)
self.assertRegex(runner.error, self.VALUE_DOES_NOT_FALL_WITHIN_EXPECTED_RANGE_MSG)
callback = 'a' * 33 # callback length can not be greater than 32
runner.call_contract(path, 'oracle_call', test_url, filter, callback, user_data, gas_for_response)
runner.execute()
self.assertEqual(VMState.FAULT, runner.vm_state, msg=runner.cli_log)
self.assertRegex(runner.error, self.VALUE_DOES_NOT_FALL_WITHIN_EXPECTED_RANGE_MSG)
def test_oracle_request_invalid_filter(self):
path, _ = self.get_deploy_file_paths('OracleRequestCall.py')
runner = NeoTestRunner(runner_id=self.method_name())
test_url = 'https://unittest.fake.url/api/0/'
callback = 'callback_method'
user_data = 'Any Data Here'
gas_for_response = 1 * 10 ** 8
from boa3_test.test_drive import neoxp
genesis = neoxp.utils.get_account_by_name('genesis')
runner.oracle_enable(genesis)
filter = "a" * 129 # filter length can not be greater than 128
runner.call_contract(path, 'oracle_call', test_url, filter, callback, user_data, gas_for_response)
runner.execute()
self.assertEqual(VMState.FAULT, runner.vm_state, msg=runner.cli_log)
self.assertRegex(runner.error, self.VALUE_DOES_NOT_FALL_WITHIN_EXPECTED_RANGE_MSG)

Platform:

  • OS: Windows 10 x64
  • Python version: Python 3.7

(Optional) Additional context
There was a problem when using a string that had white space, instead of writing "A string here", it would write ""A string here"" on the batch file.

for arg in self._args:
command.append(arg)

@luc10921 luc10921 requested a review from meevee98 July 17, 2023 14:40
@luc10921 luc10921 self-assigned this Jul 17, 2023
@lllwvlvwlll
Copy link
Member

Comment on lines 60 to 69
tx._attributes = []
if 'attributes' in json:
attributes_json = json['attributes']

for attribute in attributes_json:
from boa3_test.test_drive.model.network.payloads.transactionattribute import \
TransactionAttributeType
if attribute['type'] == TransactionAttributeType.ORACLE_RESPONSE:
from boa3_test.test_drive.model.network.payloads.oracleresponse import OracleResponse
tx._attributes.append(OracleResponse.from_json(attribute))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's better to move this logic to TransactionAttribute and import there the method from the respective classes to deserialize the given json. Something like the following:

Suggested change
tx._attributes = []
if 'attributes' in json:
attributes_json = json['attributes']
for attribute in attributes_json:
from boa3_test.test_drive.model.network.payloads.transactionattribute import \
TransactionAttributeType
if attribute['type'] == TransactionAttributeType.ORACLE_RESPONSE:
from boa3_test.test_drive.model.network.payloads.oracleresponse import OracleResponse
tx._attributes.append(OracleResponse.from_json(attribute))
if 'attributes' in json:
tx._attributes = [TransactionAttribute.from_json(atttribute) for attribute in json['attributes']]
else:
tx._attributes = []

Comment on lines 22 to 26
@staticmethod
def from_json(json: Dict[str, Any]) -> TransactionAttribute:
tx_attr = TransactionAttribute(json['type'])

return tx_attr
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Following the above suggestion

Suggested change
@staticmethod
def from_json(json: Dict[str, Any]) -> TransactionAttribute:
tx_attr = TransactionAttribute(json['type'])
return tx_attr
@staticmethod
def from_json(cls, json: Dict[str, Any]) -> TransactionAttribute:
tx_type = json['type']
if tx_type == TransactionAttributeType.ORACLE_RESPONSE:
from boa3_test.test_drive.model.network.payloads.oracleresponse import OracleResponse
tx_attr = OracleResponse._from_json(json)
else:
tx_attr = TransactionAttribute._from_json(json)
return tx_attr
@staticmethod
def _from_json(cls, json: Dict[str, Any]) -> TransactionAttribute:
# this one is implemented by OracleResponse
tx_attr = TransactionAttribute(json['type'])
return tx_attr

@coveralls
Copy link
Collaborator

coveralls commented Jul 17, 2023

Coverage Status

coverage: 91.897% (+0.009%) from 91.888% when pulling 2b4fc50 on CU-864ejer8v into aa31ab8 on development.

@luc10921 luc10921 requested a review from meevee98 July 18, 2023 16:49
@meevee98 meevee98 merged commit 7bab212 into development Jul 18, 2023
@meevee98 meevee98 deleted the CU-864ejer8v branch July 18, 2023 18:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants