diff --git a/boa3/builtin/interop/storage/__init__.py b/boa3/builtin/interop/storage/__init__.py index bc88bd1c5..b9751748a 100644 --- a/boa3/builtin/interop/storage/__init__.py +++ b/boa3/builtin/interop/storage/__init__.py @@ -9,6 +9,13 @@ 'get_uint160', 'get_uint256', 'get_ecpoint', + 'try_get', + 'try_get_int', + 'try_get_bool', + 'try_get_str', + 'try_get_uint160', + 'try_get_uint256', + 'try_get_ecpoint', 'get_context', 'get_read_only_context', 'put', @@ -55,7 +62,7 @@ def get(key: bytes, context: StorageContext = get_context()) -> bytes: b'test' >>> get(b'fake_key') - '' + b'' :param key: value identifier in the store :type key: bytes @@ -67,6 +74,28 @@ def get(key: bytes, context: StorageContext = get_context()) -> bytes: pass +@deprecated(details='This module is deprecated. Use boa3.sc.storage instead') +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 + + @deprecated(details='This module is deprecated. Use boa3.sc.storage instead') def get_int(key: bytes, context: StorageContext = get_context()) -> int: """ @@ -78,7 +107,7 @@ def get_int(key: bytes, context: StorageContext = get_context()) -> int: 5 >>> get_int(b'fake_key') - '' + 0 :param key: value identifier in the store :type key: bytes @@ -90,6 +119,28 @@ def get_int(key: bytes, context: StorageContext = get_context()) -> int: pass +@deprecated(details='This module is deprecated. Use boa3.sc.storage instead') +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 + + @deprecated(details='This module is deprecated. Use boa3.sc.storage instead') def get_bool(key: bytes, context: StorageContext = get_context()) -> bool: """ @@ -101,7 +152,7 @@ def get_bool(key: bytes, context: StorageContext = get_context()) -> bool: True >>> get_bool(b'fake_key') - '' + False :param key: value identifier in the store :type key: bytes @@ -113,6 +164,28 @@ def get_bool(key: bytes, context: StorageContext = get_context()) -> bool: pass +@deprecated(details='This module is deprecated. Use boa3.sc.storage instead') +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 + + @deprecated(details='This module is deprecated. Use boa3.sc.storage instead') def get_str(key: bytes, context: StorageContext = get_context()) -> str: """ @@ -136,6 +209,28 @@ def get_str(key: bytes, context: StorageContext = get_context()) -> str: pass +@deprecated(details='This module is deprecated. Use boa3.sc.storage instead') +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 + + @deprecated(details='This module is deprecated. Use boa3.sc.storage instead') def get_uint160(key: bytes, context: StorageContext = get_context()) -> UInt160: """ @@ -147,7 +242,7 @@ def get_uint160(key: bytes, context: StorageContext = get_context()) -> UInt160: UInt160(0x4a49484746454443424139383736353433323130) >>> get_uint160(b'fake_key') - '' + UInt160(0x0000000000000000000000000000000000000000) :param key: value identifier in the store :type key: bytes @@ -159,6 +254,28 @@ def get_uint160(key: bytes, context: StorageContext = get_context()) -> UInt160: pass +@deprecated(details='This module is deprecated. Use boa3.sc.storage instead') +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 + + @deprecated(details='This module is deprecated. Use boa3.sc.storage instead') def get_uint256(key: bytes, context: StorageContext = get_context()) -> UInt256: """ @@ -170,7 +287,7 @@ def get_uint256(key: bytes, context: StorageContext = get_context()) -> UInt256: UInt256(0x565554535251504f4e4d4c4b4a49484746454443424139383736353433323130) >>> get_uint160(b'fake_key') - '' + UInt256(0x0000000000000000000000000000000000000000000000000000000000000000) :param key: value identifier in the store :type key: bytes @@ -182,6 +299,28 @@ def get_uint256(key: bytes, context: StorageContext = get_context()) -> UInt256: pass +@deprecated(details='This module is deprecated. Use boa3.sc.storage instead') +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 + + @deprecated(details='This module is deprecated. Use boa3.sc.storage instead') def get_ecpoint(key: bytes, context: StorageContext = get_context()) -> ECPoint: """ @@ -193,7 +332,7 @@ def get_ecpoint(key: bytes, context: StorageContext = get_context()) -> ECPoint: ECPoint(0x57565554535251504f4e4d4c4b4a49484746454443424139383736353433323130) >>> get_ecpoint(b'fake_key') - '' + ECPoint(0x000000000000000000000000000000000000000000000000000000000000000000) :param key: value identifier in the store :type key: bytes @@ -205,6 +344,28 @@ def get_ecpoint(key: bytes, context: StorageContext = get_context()) -> ECPoint: pass +@deprecated(details='This module is deprecated. Use boa3.sc.storage instead') +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 + + @deprecated(details='This module is deprecated. Use boa3.sc.storage instead') def get_read_only_context() -> StorageContext: """ diff --git a/boa3/internal/model/builtin/interop/interop.py b/boa3/internal/model/builtin/interop/interop.py index 41debd4ba..bd777647d 100644 --- a/boa3/internal/model/builtin/interop/interop.py +++ b/boa3/internal/model/builtin/interop/interop.py @@ -199,6 +199,13 @@ def interop_events(cls) -> list[Event]: StorageGetUInt160 = StorageGetUInt160Method() StorageGetUInt256 = StorageGetUInt256Method() StorageGetECPoint = StorageGetECPointMethod() + StorageGetCheck = StorageTryGetBytesMethod() + StorageGetCheckInt = StorageTryGetIntMethod() + StorageGetCheckBool = StorageTryGetBoolMethod() + StorageGetCheckStr = StorageTryGetStrMethod() + StorageGetCheckUInt160 = StorageTryGetUInt160Method() + StorageGetCheckUInt256 = StorageTryGetUInt256Method() + StorageGetCheckECPoint = StorageTryGetECPointMethod() StoragePut = StoragePutBytesMethod() StoragePutInt = StoragePutIntMethod() StoragePutBool = StoragePutBoolMethod() @@ -453,6 +460,13 @@ def interop_events(cls) -> list[Event]: StorageGetUInt160, StorageGetUInt256, StorageGetECPoint, + StorageGetCheck, + StorageGetCheckInt, + StorageGetCheckBool, + StorageGetCheckStr, + StorageGetCheckUInt160, + StorageGetCheckUInt256, + StorageGetCheckECPoint, StorageGetContext, StorageGetReadOnlyContext, StoragePut, diff --git a/boa3/internal/model/builtin/interop/storage/__init__.py b/boa3/internal/model/builtin/interop/storage/__init__.py index 219390143..7daf04893 100644 --- a/boa3/internal/model/builtin/interop/storage/__init__.py +++ b/boa3/internal/model/builtin/interop/storage/__init__.py @@ -18,11 +18,19 @@ 'StoragePutIntMethod', 'StoragePutStrMethod', 'StoragePutUInt160Method', - 'StoragePutUInt256Method' + 'StoragePutUInt256Method', + 'StorageTryGetBoolMethod', + 'StorageTryGetBytesMethod', + 'StorageTryGetECPointMethod', + 'StorageTryGetIntMethod', + 'StorageTryGetStrMethod', + 'StorageTryGetUInt160Method', + 'StorageTryGetUInt256Method', ] from boa3.internal.model.builtin.interop.storage.findoptionstype import FindOptionsType from boa3.internal.model.builtin.interop.storage.get import * +from boa3.internal.model.builtin.interop.storage.getcheck import * from boa3.internal.model.builtin.interop.storage.put import * from boa3.internal.model.builtin.interop.storage.storagecontext import StorageContextType from boa3.internal.model.builtin.interop.storage.storagedeletemethod import StorageDeleteMethod diff --git a/boa3/internal/model/builtin/interop/storage/getcheck/__init__.py b/boa3/internal/model/builtin/interop/storage/getcheck/__init__.py new file mode 100644 index 000000000..a641bfdd4 --- /dev/null +++ b/boa3/internal/model/builtin/interop/storage/getcheck/__init__.py @@ -0,0 +1,7 @@ +from boa3.internal.model.builtin.interop.storage.getcheck.storagegetboolmethod import StorageTryGetBoolMethod +from boa3.internal.model.builtin.interop.storage.getcheck.storagegetbytesmethod import StorageTryGetBytesMethod +from boa3.internal.model.builtin.interop.storage.getcheck.storagegetecpointmethod import StorageTryGetECPointMethod +from boa3.internal.model.builtin.interop.storage.getcheck.storagegetintmethod import StorageTryGetIntMethod +from boa3.internal.model.builtin.interop.storage.getcheck.storagegetstrmethod import StorageTryGetStrMethod +from boa3.internal.model.builtin.interop.storage.getcheck.storagegetuint160method import StorageTryGetUInt160Method +from boa3.internal.model.builtin.interop.storage.getcheck.storagegetuint256method import StorageTryGetUInt256Method diff --git a/boa3/internal/model/builtin/interop/storage/getcheck/istoragetrygetmethod.py b/boa3/internal/model/builtin/interop/storage/getcheck/istoragetrygetmethod.py new file mode 100644 index 000000000..04ed56f5f --- /dev/null +++ b/boa3/internal/model/builtin/interop/storage/getcheck/istoragetrygetmethod.py @@ -0,0 +1,118 @@ +import abc +import ast +from collections.abc import Iterable, Sized +from typing import Any + +from boa3.internal.model import set_internal_call +from boa3.internal.model.builtin.interop.interopmethod import InteropMethod +from boa3.internal.model.builtin.method.builtinmethod import IBuiltinMethod +from boa3.internal.model.expression import IExpression +from boa3.internal.model.type.itype import IType +from boa3.internal.model.variable import Variable + + +class IStorageTryGetMethod(InteropMethod, abc.ABC): + + def __init__(self, identifier: str, value_type: IType): + from boa3.internal.model.type.type import Type + from boa3.internal.model.builtin.interop.storage.storagecontext.storagecontexttype import StorageContextType + + syscall = 'System.Storage.Get' + context_type = StorageContextType.build() + + args: dict[str, Variable] = {'key': Variable(Type.bytes), + 'context': Variable(context_type)} + + from boa3.internal.model.builtin.interop.storage.storagegetcontextmethod import StorageGetContextMethod + default_id = StorageGetContextMethod(context_type).identifier + context_default = set_internal_call(ast.parse(f"{default_id}()" + ).body[0].value) + + return_type = Type.tuple.build((value_type, Type.bool)) + super().__init__(identifier, syscall, args, defaults=[context_default], return_type=return_type) + + @abc.abstractmethod + def generate_default_value_opcodes(self, code_generator): + pass + + @abc.abstractmethod + def generate_deserialize_value_opcodes(self, code_generator): + pass + + def generate_internal_opcodes(self, code_generator): + super().generate_internal_opcodes(code_generator) + + from boa3.internal.model.operation.unaryop import UnaryOp + + # exists = result is not None + code_generator.duplicate_stack_top_item() + code_generator.insert_type_check(None) + code_generator.convert_operation(UnaryOp.Not, is_internal=True) + + # if exists: + code_generator.swap_reverse_stack_items(2) + code_generator.duplicate_stack_item(2) + if_is_null = code_generator.convert_begin_if() + + # result = deserialize(result) + self.generate_deserialize_value_opcodes(code_generator) + + # else + else_is_not_null = code_generator.convert_begin_else(if_is_null, is_internal=True) + # result = default_value + code_generator.remove_stack_top_item() + self.generate_default_value_opcodes(code_generator) + + if else_is_not_null < code_generator.last_code_start_address: + if_is_null = else_is_not_null + code_generator.convert_end_if(if_is_null, is_internal=True) + + code_generator.convert_new_array(2, self.return_type) + + @property + def generation_order(self) -> list[int]: + """ + Gets the indexes order that need to be used during code generation. + If the order for generation is the same as inputted in code, returns reversed(range(0,len_args)) + + :return: Index order for code generation + """ + indexes = super().generation_order + context_index = list(self.args).index('context') + + if indexes[-1] != context_index: + # context must be the last generated argument + indexes.remove(context_index) + indexes.append(context_index) + + return indexes + + @property + def key_arg(self) -> Variable: + return self.args['key'] + + def build(self, value: Any) -> IBuiltinMethod: + exp: list[IExpression] = [] + if isinstance(value, Sized): + if len(value) > 1 or not isinstance(value, Iterable): + return self + exp = [exp if isinstance(exp, IExpression) else Variable(exp) + for exp in value if isinstance(exp, (IExpression, IType))] + + elif isinstance(exp, (IExpression, IType)): + exp = [value if isinstance(value, IExpression) else Variable(value)] + else: + return self + + if not self.validate_parameters(*exp): + return self + + method = self + key_type: IType = exp[0].type + if method.key_arg.type.is_type_of(key_type): + return self + else: + from boa3.internal.model.builtin.interop.storage.get.storagegetbytesmethod import StorageGetBytesMethod + method = StorageGetBytesMethod() + method.args['key'] = Variable(key_type) + return method diff --git a/boa3/internal/model/builtin/interop/storage/getcheck/storagegetboolmethod.py b/boa3/internal/model/builtin/interop/storage/getcheck/storagegetboolmethod.py new file mode 100644 index 000000000..7087888e0 --- /dev/null +++ b/boa3/internal/model/builtin/interop/storage/getcheck/storagegetboolmethod.py @@ -0,0 +1,22 @@ +from boa3.internal.model.builtin.interop.storage.getcheck.istoragetrygetmethod import IStorageTryGetMethod + + +class StorageTryGetBoolMethod(IStorageTryGetMethod): + def __init__(self): + from boa3.internal.model.type.type import Type + + identifier = 'try_get_bool' + value_type = Type.bool + + super().__init__(identifier, value_type=value_type) + + def generate_default_value_opcodes(self, code_generator): + # default_value = 0 + code_generator.convert_literal(0) + + def generate_deserialize_value_opcodes(self, code_generator): + from boa3.internal.model.builtin.method.toboolmethod import ToBool + from boa3.internal.model.type.type import Type + + converter = ToBool.build(Type.bytes) + code_generator.convert_builtin_method_call(converter) diff --git a/boa3/internal/model/builtin/interop/storage/getcheck/storagegetbytesmethod.py b/boa3/internal/model/builtin/interop/storage/getcheck/storagegetbytesmethod.py new file mode 100644 index 000000000..883f8e1be --- /dev/null +++ b/boa3/internal/model/builtin/interop/storage/getcheck/storagegetbytesmethod.py @@ -0,0 +1,19 @@ +from boa3.internal.model.builtin.interop.storage.getcheck.istoragetrygetmethod import IStorageTryGetMethod + + +class StorageTryGetBytesMethod(IStorageTryGetMethod): + def __init__(self): + from boa3.internal.model.type.type import Type + + identifier = 'try_get' + value_type = Type.bytes + + super().__init__(identifier, value_type=value_type) + + def generate_default_value_opcodes(self, code_generator): + # default_value = b'' + code_generator.convert_literal(b'') + + def generate_deserialize_value_opcodes(self, code_generator): + # it doesn't need to deserialize bytes values + return diff --git a/boa3/internal/model/builtin/interop/storage/getcheck/storagegetecpointmethod.py b/boa3/internal/model/builtin/interop/storage/getcheck/storagegetecpointmethod.py new file mode 100644 index 000000000..bef58505b --- /dev/null +++ b/boa3/internal/model/builtin/interop/storage/getcheck/storagegetecpointmethod.py @@ -0,0 +1,20 @@ +from boa3.internal.model.builtin.interop.storage.getcheck.istoragetrygetmethod import IStorageTryGetMethod + + +class StorageTryGetECPointMethod(IStorageTryGetMethod): + def __init__(self): + from boa3.internal.model.type.collection.sequence.ecpointtype import ECPointType + + identifier = 'try_get_ecpoint' + value_type = ECPointType.build() + + super().__init__(identifier, value_type=value_type) + + def generate_default_value_opcodes(self, code_generator): + from boa3.internal.model.builtin.builtin import Builtin + + code_generator.convert_literal(Builtin.ECPoint.default_value) + + def generate_deserialize_value_opcodes(self, code_generator): + # it doesn't need to deserialize bytes values + return diff --git a/boa3/internal/model/builtin/interop/storage/getcheck/storagegetintmethod.py b/boa3/internal/model/builtin/interop/storage/getcheck/storagegetintmethod.py new file mode 100644 index 000000000..fd615f79c --- /dev/null +++ b/boa3/internal/model/builtin/interop/storage/getcheck/storagegetintmethod.py @@ -0,0 +1,22 @@ +from boa3.internal.model.builtin.interop.storage.getcheck.istoragetrygetmethod import IStorageTryGetMethod + + +class StorageTryGetIntMethod(IStorageTryGetMethod): + def __init__(self): + from boa3.internal.model.type.type import Type + + identifier = 'try_get_int' + value_type = Type.int + + super().__init__(identifier, value_type=value_type) + + def generate_default_value_opcodes(self, code_generator): + # default_value = 0 + code_generator.convert_literal(0) + + def generate_deserialize_value_opcodes(self, code_generator): + from boa3.internal.model.builtin.method.tointmethod import ToInt + from boa3.internal.model.type.type import Type + + converter = ToInt.build(Type.bytes) + code_generator.convert_builtin_method_call(converter) diff --git a/boa3/internal/model/builtin/interop/storage/getcheck/storagegetstrmethod.py b/boa3/internal/model/builtin/interop/storage/getcheck/storagegetstrmethod.py new file mode 100644 index 000000000..65402a3c0 --- /dev/null +++ b/boa3/internal/model/builtin/interop/storage/getcheck/storagegetstrmethod.py @@ -0,0 +1,22 @@ +from boa3.internal.model.builtin.interop.storage.getcheck.istoragetrygetmethod import IStorageTryGetMethod + + +class StorageTryGetStrMethod(IStorageTryGetMethod): + def __init__(self): + from boa3.internal.model.type.type import Type + + identifier = 'try_get_str' + value_type = Type.str + + super().__init__(identifier, value_type=value_type) + + def generate_default_value_opcodes(self, code_generator): + # default_value = '' + code_generator.convert_literal('') + + def generate_deserialize_value_opcodes(self, code_generator): + from boa3.internal.model.builtin.method.tostrmethod import ToStr + from boa3.internal.model.type.type import Type + + converter = ToStr.build(Type.bytes) + code_generator.convert_builtin_method_call(converter) diff --git a/boa3/internal/model/builtin/interop/storage/getcheck/storagegetuint160method.py b/boa3/internal/model/builtin/interop/storage/getcheck/storagegetuint160method.py new file mode 100644 index 000000000..217688253 --- /dev/null +++ b/boa3/internal/model/builtin/interop/storage/getcheck/storagegetuint160method.py @@ -0,0 +1,20 @@ +from boa3.internal.model.builtin.interop.storage.getcheck.istoragetrygetmethod import IStorageTryGetMethod + + +class StorageTryGetUInt160Method(IStorageTryGetMethod): + def __init__(self): + from boa3.internal.model.type.collection.sequence.uint160type import UInt160Type + + identifier = 'try_get_uint160' + value_type = UInt160Type.build() + + super().__init__(identifier, value_type=value_type) + + def generate_default_value_opcodes(self, code_generator): + from boa3.internal.model.builtin.builtin import Builtin + + code_generator.convert_literal(Builtin.UInt160.default_value) + + def generate_deserialize_value_opcodes(self, code_generator): + # it doesn't need to deserialize bytes values + return diff --git a/boa3/internal/model/builtin/interop/storage/getcheck/storagegetuint256method.py b/boa3/internal/model/builtin/interop/storage/getcheck/storagegetuint256method.py new file mode 100644 index 000000000..2142503d9 --- /dev/null +++ b/boa3/internal/model/builtin/interop/storage/getcheck/storagegetuint256method.py @@ -0,0 +1,20 @@ +from boa3.internal.model.builtin.interop.storage.getcheck.istoragetrygetmethod import IStorageTryGetMethod + + +class StorageTryGetUInt256Method(IStorageTryGetMethod): + def __init__(self): + from boa3.internal.model.type.collection.sequence.uint256type import UInt256Type + + identifier = 'try_get_uint256' + value_type = UInt256Type.build() + + super().__init__(identifier, value_type=value_type) + + def generate_default_value_opcodes(self, code_generator): + from boa3.internal.model.builtin.builtin import Builtin + + code_generator.convert_literal(Builtin.UInt256.default_value) + + def generate_deserialize_value_opcodes(self, code_generator): + # it doesn't need to deserialize bytes values + return diff --git a/boa3/internal/model/builtin/interop/storage/storagecontext/storagecontexttype.py b/boa3/internal/model/builtin/interop/storage/storagecontext/storagecontexttype.py index af5960040..c4ee6dd6e 100644 --- a/boa3/internal/model/builtin/interop/storage/storagecontext/storagecontexttype.py +++ b/boa3/internal/model/builtin/interop/storage/storagecontext/storagecontexttype.py @@ -1,6 +1,4 @@ -from __future__ import annotations - -from typing import Any +from typing import Any, Self from boa3.internal.model.builtin.interop.interopinterfacetype import InteropInterfaceType from boa3.internal.model.method import Method @@ -59,7 +57,7 @@ def constructor_method(self) -> Method | None: return self._constructor @classmethod - def build(cls, value: Any = None) -> StorageContextType: + def build(cls, value: Any = None) -> Self: if value is None or cls._is_type_of(value): return _StorageContext diff --git a/boa3/internal/model/builtin/interop/storage/storagemap/storagemaptype.py b/boa3/internal/model/builtin/interop/storage/storagemap/storagemaptype.py index 66bf999cb..5150bacaa 100644 --- a/boa3/internal/model/builtin/interop/storage/storagemap/storagemaptype.py +++ b/boa3/internal/model/builtin/interop/storage/storagemap/storagemaptype.py @@ -1,6 +1,4 @@ -from __future__ import annotations - -from typing import Any +from typing import Any, Self from boa3.internal.model.builtin.method.builtinmethod import IBuiltinMethod from boa3.internal.model.expression import IExpression @@ -78,7 +76,7 @@ def constructor_method(self) -> Method | None: return self._constructor @classmethod - def build(cls, value: Any = None) -> StorageMapType: + def build(cls, value: Any = None) -> Self: if value is None or cls._is_type_of(value): return _StorageMap diff --git a/boa3/internal/model/sc/__init__.py b/boa3/internal/model/sc/__init__.py index 07a28348c..051a41afe 100644 --- a/boa3/internal/model/sc/__init__.py +++ b/boa3/internal/model/sc/__init__.py @@ -47,6 +47,13 @@ def package_symbols(cls, package: str = None) -> Package | None: Interop.StorageGetUInt256, Interop.StorageGetECPoint, Interop.StorageGetContext, + Interop.StorageGetCheck, + Interop.StorageGetCheckInt, + Interop.StorageGetCheckBool, + Interop.StorageGetCheckStr, + Interop.StorageGetCheckUInt160, + Interop.StorageGetCheckUInt256, + Interop.StorageGetCheckECPoint, Interop.StorageGetReadOnlyContext, Interop.StoragePut, Interop.StoragePutInt, diff --git a/boa3/sc/storage/__init__.py b/boa3/sc/storage/__init__.py index caf845889..2418f8457 100644 --- a/boa3/sc/storage/__init__.py +++ b/boa3/sc/storage/__init__.py @@ -8,6 +8,13 @@ 'get_uint160', 'get_uint256', 'get_ecpoint', + 'try_get', + 'try_get_int', + 'try_get_bool', + 'try_get_str', + 'try_get_uint160', + 'try_get_uint256', + 'try_get_ecpoint', 'get_context', 'get_read_only_context', 'put', @@ -50,7 +57,7 @@ def get(key: bytes, context: StorageContext = get_context()) -> bytes: b'test' >>> get(b'fake_key') - '' + b'' :param key: value identifier in the store :type key: bytes @@ -62,6 +69,27 @@ def get(key: bytes, context: StorageContext = get_context()) -> 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. @@ -72,7 +100,7 @@ def get_int(key: bytes, context: StorageContext = get_context()) -> int: 5 >>> get_int(b'fake_key') - '' + 0 :param key: value identifier in the store :type key: bytes @@ -84,6 +112,27 @@ def get_int(key: bytes, context: StorageContext = get_context()) -> 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. @@ -94,7 +143,7 @@ def get_bool(key: bytes, context: StorageContext = get_context()) -> bool: True >>> get_bool(b'fake_key') - '' + False :param key: value identifier in the store :type key: bytes @@ -106,6 +155,27 @@ def get_bool(key: bytes, context: StorageContext = get_context()) -> 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. @@ -128,6 +198,27 @@ def get_str(key: bytes, context: StorageContext = get_context()) -> 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. @@ -138,7 +229,7 @@ def get_uint160(key: bytes, context: StorageContext = get_context()) -> UInt160: UInt160(0x4a49484746454443424139383736353433323130) >>> get_uint160(b'fake_key') - '' + UInt160(0x0000000000000000000000000000000000000000) :param key: value identifier in the store :type key: bytes @@ -150,6 +241,27 @@ def get_uint160(key: bytes, context: StorageContext = get_context()) -> 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. @@ -160,7 +272,7 @@ def get_uint256(key: bytes, context: StorageContext = get_context()) -> UInt256: UInt256(0x565554535251504f4e4d4c4b4a49484746454443424139383736353433323130) >>> get_uint160(b'fake_key') - '' + UInt256(0x0000000000000000000000000000000000000000000000000000000000000000) :param key: value identifier in the store :type key: bytes @@ -172,6 +284,27 @@ def get_uint256(key: bytes, context: StorageContext = get_context()) -> 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. @@ -182,7 +315,7 @@ def get_ecpoint(key: bytes, context: StorageContext = get_context()) -> ECPoint: ECPoint(0x57565554535251504f4e4d4c4b4a49484746454443424139383736353433323130) >>> get_ecpoint(b'fake_key') - '' + ECPoint(0x000000000000000000000000000000000000000000000000000000000000000000) :param key: value identifier in the store :type key: bytes @@ -194,6 +327,27 @@ def get_ecpoint(key: bytes, context: StorageContext = get_context()) -> 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 + + def get_read_only_context() -> StorageContext: """ Gets current read only storage context. diff --git a/boa3_test/test_sc/interop_test/storage/StorageTryGet.py b/boa3_test/test_sc/interop_test/storage/StorageTryGet.py new file mode 100644 index 000000000..f9d9ce4b4 --- /dev/null +++ b/boa3_test/test_sc/interop_test/storage/StorageTryGet.py @@ -0,0 +1,12 @@ +from boa3.builtin.compile_time import public +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) diff --git a/boa3_test/test_sc/interop_test/storage/StorageTryGetExists.py b/boa3_test/test_sc/interop_test/storage/StorageTryGetExists.py new file mode 100644 index 000000000..6fd543c0b --- /dev/null +++ b/boa3_test/test_sc/interop_test/storage/StorageTryGetExists.py @@ -0,0 +1,12 @@ +from boa3.builtin.compile_time import public +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) -> bool: + return try_get_int(key)[1] diff --git a/boa3_test/test_sc/interop_test/storage/StorageTryGetValue.py b/boa3_test/test_sc/interop_test/storage/StorageTryGetValue.py new file mode 100644 index 000000000..dd744255a --- /dev/null +++ b/boa3_test/test_sc/interop_test/storage/StorageTryGetValue.py @@ -0,0 +1,12 @@ +from boa3.builtin.compile_time import public +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) -> int: + return try_get_int(key)[0] diff --git a/boa3_test/tests/compiler_tests/test_interop/test_storage.py b/boa3_test/tests/compiler_tests/test_interop/test_storage.py index d562d83b5..6fbbb4f97 100644 --- a/boa3_test/tests/compiler_tests/test_interop/test_storage.py +++ b/boa3_test/tests/compiler_tests/test_interop/test_storage.py @@ -18,7 +18,7 @@ class TestStorageInterop(boatestcase.BoaTestCase): storage_put_hash = Interop.StoragePut.interop_method_hash storage_delete_hash = Interop.StorageDelete.interop_method_hash - def test_storage_get_bytes_key(self): + def test_storage_get_bytes_key_compile(self): expected_output = ( Opcode.INITSLOT + b'\x00' @@ -44,12 +44,88 @@ def test_storage_get_bytes_key(self): output, _ = self.assertCompilerLogs(CompilerWarning.DeprecatedSymbol, 'StorageGetDeprecated.py') self.assertEqual(expected_output, output) + async def test_storage_get_bytes_key_run(self): + await self.set_up_contract('StorageGetBytesKey.py') + + result, _ = await self.call('Main', [b'123'], return_type=bytes) + self.assertEqual(b'', result) + 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_try_get(self): + await self.set_up_contract('StorageTryGet.py') + + 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]) + + async def test_storage_try_get_value(self): + await self.set_up_contract('StorageTryGetValue.py') + + key = b'example_key' + value = 42 + + expected = 0 + result, _ = await self.call('get_value', [key], return_type=int) + 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) + + result, _ = await self.call('get_value', [key], return_type=int) + self.assertEqual(value, result) + + contract_storage = await self.get_storage(values_post_processor=storage.as_int) + self.assertIn(key, contract_storage) + self.assertEqual(value, contract_storage[key]) + + async def test_storage_try_get_exists(self): + await self.set_up_contract('StorageTryGetExists.py') + + key = b'example_key' + value = 42 + + contract_storage = await self.get_storage(values_post_processor=storage.as_int) + self.assertNotIn(key, contract_storage) + + expected = key in contract_storage + result, _ = await self.call('get_value', [key], return_type=bool) + self.assertEqual(expected, result) + + result, _ = await self.call('put_value', [key, value], return_type=None, signing_accounts=[self.genesis]) + self.assertIsNone(result) + + contract_storage = await self.get_storage(values_post_processor=storage.as_int) + self.assertIn(key, contract_storage) + + expected = key in contract_storage + result, _ = await self.call('get_value', [key], return_type=bool) + self.assertEqual(expected, result) + async def test_storage_put_bytes_key_bytes_value(self): await self.set_up_contract('StoragePutBytesKeyBytesValue.py')