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

#86dt6db2x - Improve new get methods to avoid returning misleading sentinel values #1244

Merged
merged 2 commits into from
May 2, 2024

Conversation

meevee98
Copy link
Contributor

Summary or solution description
Implemented a new method for each storage.get that gets the value saved in the smart contract and whether the key actually is in the storage or the returned value is the return type default value.

def get(key: bytes, context: StorageContext = get_context()) -> bytes:
"""
Gets a value from the persistent store based on the given key.
>>> put(b'unit', 'test')
... get(b'unit')
b'test'
>>> get(b'fake_key')
b''
:param key: value identifier in the store
:type key: bytes
:param context: storage context to be used
:type context: StorageContext
:return: the value corresponding to given key for current storage context
:rtype: bytes
"""
pass
def try_get(key: bytes, context: StorageContext = get_context()) -> tuple[bytes, bool]:
"""
Gets a value from the persistent store based on the given key and returns whether the value is stored.
>>> put(b'unit', 'test')
... try_get(b'unit')
(b'test', True)
>>> try_get(b'fake_key')
(b'', False)
:param key: value identifier in the store
:type key: bytes
:param context: storage context to be used
:type context: StorageContext
:return: the value corresponding to given key for current storage context and whether it was actually stored
:rtype: tuple[bytes, bool]
"""
pass
def get_int(key: bytes, context: StorageContext = get_context()) -> int:
"""
Gets a value as integer from the persistent store based on the given key.
It's equivalent to boa3.builtin.type.helper.to_int(get(key, context))
>>> put_int(b'unit', 5)
... get_int(b'unit')
5
>>> get_int(b'fake_key')
0
:param key: value identifier in the store
:type key: bytes
:param context: storage context to be used
:type context: StorageContext
:return: the value corresponding to given key for current storage context
:rtype: int
"""
pass
def try_get_int(key: bytes, context: StorageContext = get_context()) -> tuple[int, bool]:
"""
Gets a value as integer from the persistent store based on the given key and returns whether the value is stored.
>>> put_int(b'unit', 5)
... try_get_int(b'unit')
(5, True)
>>> try_get_int(b'fake_key')
(0, False)
:param key: value identifier in the store
:type key: bytes
:param context: storage context to be used
:type context: StorageContext
:return: the value corresponding to given key for current storage context and whether it was actually stored
:rtype: tuple[int, bool]
"""
pass
def get_bool(key: bytes, context: StorageContext = get_context()) -> bool:
"""
Gets a value as boolean from the persistent store based on the given key.
It's equivalent to boa3.builtin.type.helper.to_bool(get(key, context))
>>> put_bool(b'unit', True)
... get_bool(b'unit')
True
>>> get_bool(b'fake_key')
False
:param key: value identifier in the store
:type key: bytes
:param context: storage context to be used
:type context: StorageContext
:return: the value corresponding to given key for current storage context
:rtype: bool
"""
pass
def try_get_bool(key: bytes, context: StorageContext = get_context()) -> tuple[bool, bool]:
"""
Gets a value as boolean from the persistent store based on the given key and returns whether the value is stored.
>>> put_bool(b'unit', False)
... try_get_bool(b'unit')
(False, True)
>>> try_get_bool(b'fake_key')
(False, False)
:param key: value identifier in the store
:type key: bytes
:param context: storage context to be used
:type context: StorageContext
:return: the value corresponding to given key for current storage context and whether it was actually stored
:rtype: tuple[bool, bool]
"""
pass
def get_str(key: bytes, context: StorageContext = get_context()) -> str:
"""
Gets a value as string from the persistent store based on the given key.
It's equivalent to boa3.builtin.type.helper.to_str(get(key, context))
>>> put_str(b'unit', 'test')
... get_str(b'unit')
'test'
>>> get_str(b'fake_key')
''
:param key: value identifier in the store
:type key: bytes
:param context: storage context to be used
:type context: StorageContext
:return: the value corresponding to given key for current storage context
:rtype: str
"""
pass
def try_get_str(key: bytes, context: StorageContext = get_context()) -> tuple[str, bool]:
"""
Gets a value as string from the persistent store based on the given key and returns whether the value is stored.
>>> put_str(b'unit', 'test')
... try_get_str(b'unit')
('test', True)
>>> try_get_str(b'fake_key')
('', False)
:param key: value identifier in the store
:type key: bytes
:param context: storage context to be used
:type context: StorageContext
:return: the value corresponding to given key for current storage context and whether it was actually stored
:rtype: tuple[str, bool]
"""
pass
def get_uint160(key: bytes, context: StorageContext = get_context()) -> UInt160:
"""
Gets a value as UInt160 from the persistent store based on the given key.
It's equivalent UInt160(get(key, context))
>>> put_uint160(b'unit', UInt160(b'0123456789ABCDEFGHIJ'))
... get_uint160(b'unit')
UInt160(0x4a49484746454443424139383736353433323130)
>>> get_uint160(b'fake_key')
UInt160(0x0000000000000000000000000000000000000000)
:param key: value identifier in the store
:type key: bytes
:param context: storage context to be used
:type context: StorageContext
:return: the value corresponding to given key for current storage context
:rtype: UInt160
"""
pass
def try_get_uint160(key: bytes, context: StorageContext = get_context()) -> tuple[UInt160, bool]:
"""
Gets a value as UInt160 from the persistent store based on the given key and returns whether the value is stored.
>>> put_uint160(b'unit', UInt160(b'0123456789ABCDEFGHIJ'))
... try_get_uint160(b'unit')
(UInt160(0x4a49484746454443424139383736353433323130), True)
>>> get_uint160(b'fake_key')
(UInt160(0x0000000000000000000000000000000000000000), False)
:param key: value identifier in the store
:type key: bytes
:param context: storage context to be used
:type context: StorageContext
:return: the value corresponding to given key for current storage context and whether it was actually stored
:rtype: tuple[UInt160, bool]
"""
pass
def get_uint256(key: bytes, context: StorageContext = get_context()) -> UInt256:
"""
Gets a value as UInt256 from the persistent store based on the given key.
It's equivalent UInt256(get(key, context))
>>> put_uint256(b'unit', UInt256(b'0123456789ABCDEFGHIJKLMNOPQRSTUV'))
... get_uint256(b'unit')
UInt256(0x565554535251504f4e4d4c4b4a49484746454443424139383736353433323130)
>>> get_uint160(b'fake_key')
UInt256(0x0000000000000000000000000000000000000000000000000000000000000000)
:param key: value identifier in the store
:type key: bytes
:param context: storage context to be used
:type context: StorageContext
:return: the value corresponding to given key for current storage context
:rtype: UInt256
"""
pass
def try_get_uint256(key: bytes, context: StorageContext = get_context()) -> tuple[UInt256, bool]:
"""
Gets a value as UInt256 from the persistent store based on the given key and returns whether the value is stored.
>>> put_uint256(b'unit', UInt256(b'0123456789ABCDEFGHIJKLMNOPQRSTUV'))
... get_uint256(b'unit')
(UInt256(0x565554535251504f4e4d4c4b4a49484746454443424139383736353433323130), True)
>>> get_uint160(b'fake_key')
(UInt256(0x0000000000000000000000000000000000000000000000000000000000000000), False)
:param key: value identifier in the store
:type key: bytes
:param context: storage context to be used
:type context: StorageContext
:return: the value corresponding to given key for current storage context and whether it was actually stored
:rtype: tuple[UInt256, bool]
"""
pass
def get_ecpoint(key: bytes, context: StorageContext = get_context()) -> ECPoint:
"""
Gets a value as ECPoint from the persistent store based on the given key.
It's equivalent ECPoint(get(key, context))
>>> put_ecpoint(b'unit', ECPoint(b'0123456789ABCDEFGHIJKLMNOPQRSTUVW'))
... get_ecpoint(b'unit')
ECPoint(0x57565554535251504f4e4d4c4b4a49484746454443424139383736353433323130)
>>> get_ecpoint(b'fake_key')
ECPoint(0x000000000000000000000000000000000000000000000000000000000000000000)
:param key: value identifier in the store
:type key: bytes
:param context: storage context to be used
:type context: StorageContext
:return: the value corresponding to given key for current storage context
:rtype: ECPoint
"""
pass
def try_get_ecpoint(key: bytes, context: StorageContext = get_context()) -> tuple[ECPoint, bool]:
"""
Gets a value as ECPoint from the persistent store based on the given key and returns whether the value is stored.
>>> put_ecpoint(b'unit', ECPoint(b'0123456789ABCDEFGHIJKLMNOPQRSTUVW'))
... try_get_ecpoint(b'unit')
(ECPoint(0x57565554535251504f4e4d4c4b4a49484746454443424139383736353433323130), True)
>>> try_get_ecpoint(b'fake_key')
(ECPoint(0x000000000000000000000000000000000000000000000000000000000000000000), False)
:param key: value identifier in the store
:type key: bytes
:param context: storage context to be used
:type context: StorageContext
:return: the value corresponding to given key for current storage context and whether it was actually stored
:rtype: tuple[ECPoint, bool]
"""
pass

How to Reproduce

from boa3.sc.storage import try_get_int, put_int
@public
def put_value(key: bytes, value: int):
put_int(key, value)
@public
def get_value(key: bytes) -> tuple[int, bool]:
return try_get_int(key)

Tests

async def test_storage_get_check(self):
await self.set_up_contract('StorageGetCheck.py', compile_if_found=True)
key = b'example_key'
value = 42
expected = (0, False)
result, _ = await self.call('get_value', [key], return_type=tuple[int, bool])
self.assertEqual(expected, result)
contract_storage = await self.get_storage(values_post_processor=storage.as_int)
self.assertNotIn(key, contract_storage)
result, _ = await self.call('put_value', [key, value], return_type=None, signing_accounts=[self.genesis])
self.assertIsNone(result)
expected = (value, True)
result, _ = await self.call('get_value', [key], return_type=tuple[int, bool])
self.assertEqual(expected, result)
contract_storage = await self.get_storage(values_post_processor=storage.as_int)
self.assertIn(key, contract_storage)
self.assertEqual(value, contract_storage[key])

@meevee98 meevee98 self-assigned this Apr 29, 2024
@melanke
Copy link
Contributor

melanke commented Apr 29, 2024

@coveralls
Copy link
Collaborator

coveralls commented Apr 29, 2024

Coverage Status

coverage: 91.745% (-0.1%) from 91.859%
when pulling 441cd41 on CU-86dt6db2x
into 1ebbe22 on development.

def test_storage_get_str_key(self):
self.assertCompilerLogs(CompilerError.MismatchedTypes, 'StorageGetStrKey.py')

def test_storage_get_mismatched_type(self):
self.assertCompilerLogs(CompilerError.MismatchedTypes, 'StorageGetMismatchedType.py')

async def test_storage_get_check(self):
await self.set_up_contract('StorageGetCheck.py', compile_if_found=True)
Copy link
Contributor

Choose a reason for hiding this comment

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

compile_if_found=True should not be here


@public
def get_value(key: bytes) -> tuple[int, bool]:
return try_get_int(key)
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 would be good to have another test that uses the storage value and boolean individually, like try_get_int(key)[0] and try_get_int(key)[1]

@luc10921 luc10921 merged commit c71affa into development May 2, 2024
4 checks passed
@luc10921 luc10921 deleted the CU-86dt6db2x branch May 2, 2024 15:18
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