Skip to content
This repository has been archived by the owner on Dec 15, 2021. It is now read-only.

IS-1112: Allow list and dict as score parameter #470

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
71d8e7d
Refactor Score inspect code
Jun 21, 2020
5bfcfeb
Define Delegation parameter type hint with TypedDict in SystemScore
Jun 22, 2020
4064f59
Under development
Jun 22, 2020
9d186a0
Under development
goldworm-icon Jun 22, 2020
b64eb97
Under development
goldworm-icon Jun 23, 2020
3115b07
Implement getScoreApi functions
goldworm-icon Jun 23, 2020
3a9fc20
get_score_api is under development
goldworm-icon Jun 24, 2020
cd561eb
Exception handling on get_score_api
goldworm-icon Jun 24, 2020
46ed9ee
Move create_score_element() to typing/element.py
goldworm-icon Jun 25, 2020
835f467
Refactor ConstBitFlag for score
goldworm-icon Jun 25, 2020
27ce543
Add ScoreElementContainer
goldworm-icon Jun 25, 2020
ed22d47
Rename tests/unittest to tests/unit_test
goldworm-icon Jun 25, 2020
7a49b2a
Add typing_extensions to requirements.txt for TypedDict
Jun 25, 2020
ff444c2
Fix minor typo
Jun 25, 2020
433877d
Remove unused codes from iconscore package
Jun 25, 2020
6100f6f
Make normalize_type_hint robust
goldworm-icon Jun 26, 2020
00f5629
Minor update
Jun 27, 2020
699ae0b
Apply new type converter
Jun 28, 2020
10fe257
Fix failures of unittest for params type conversion
goldworm-icon Jun 29, 2020
1b33623
Fix unittest failures
goldworm-icon Jun 29, 2020
2cb9fa3
Remove some comment-out codes
goldworm-icon Jun 29, 2020
93244f0
Update verify_score_flag()
goldworm-icon Jun 30, 2020
83d0b4d
Allow Optional type hint
goldworm-icon Jun 30, 2020
068b9ce
Verify fallback() signature
goldworm-icon Jul 1, 2020
381bdd0
Rename ScoreElement to ScoreElementMetadata
goldworm-icon Jul 1, 2020
856fdac
Check parameter default type in normalize_parameter()
goldworm-icon Jul 1, 2020
83ca2ea
Rename get_score_element() to get_score_element_metadata()
goldworm-icon Jul 2, 2020
7e36d7e
Remove useless codes from score api definition part
goldworm-icon Jul 2, 2020
95a9067
Remove unused comments
goldworm-icon Jul 2, 2020
97837a1
Make str_to_object() allow None as a argument
goldworm-icon Jul 2, 2020
8cd416a
verify_internal_call_arguments() is under development
goldworm-icon Jul 2, 2020
2e4ff3d
Check if argument types match to type hints in parameters on internal…
goldworm-icon Jul 3, 2020
47a55b3
Fix bugs in normalize functions
goldworm-icon Jul 3, 2020
e718082
Consider Union type in type conversion and verification
goldworm-icon Jul 4, 2020
dbcbb0e
Fix minor bugs
Jul 4, 2020
e3af39a
Apply convert_score_parameters to DeployEngine
Jul 4, 2020
908328c
Skip 2 unit-tests for DeployEngine
Jul 5, 2020
a00afc3
Apply convert_score_parameters to DeployEngine
Jul 5, 2020
5b9f068
Fix some bugs in iconscore/typing package
goldworm-icon Jul 6, 2020
86dbe3a
Change supported python version: 3.7
goldworm-icon Jul 6, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions iconservice/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@
from abc import ABCMeta, abstractmethod, ABC
from functools import wraps
from inspect import isfunction
from typing import List

from iconcommons.logger import Logger
from typing_extensions import TypedDict

from .base.address import Address, AddressPrefix, SYSTEM_SCORE_ADDRESS, ZERO_SCORE_ADDRESS
from .base.exception import IconScoreException
Expand Down
24 changes: 20 additions & 4 deletions iconservice/deploy/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import inspect
import os
from typing import TYPE_CHECKING

from iconcommons import Logger

from .icon_score_deployer import IconScoreDeployer
from .utils import remove_path, get_score_path
from ..base.ComponentBase import EngineBase
Expand All @@ -33,6 +35,8 @@
from ..iconscore.icon_score_context_util import IconScoreContextUtil
from ..iconscore.icon_score_mapper_object import IconScoreInfo
from ..iconscore.icon_score_step import StepType, get_deploy_content_size
from ..iconscore.typing.conversion import convert_score_parameters
from ..iconscore.typing.element import normalize_signature
from ..iconscore.utils import get_score_deploy_path
from ..utils import is_builtin_score

Expand Down Expand Up @@ -206,13 +210,17 @@ def _on_deploy(self,
# score_info.get_score() returns a cached or created score instance
# according to context.revision.
score: 'IconScoreBase' = score_info.get_score(context.revision)
ScoreApiGenerator.check_on_deploy(context, score)

# check_on_deploy will be done by normalize_signature()
# since Revision.THREE
if context.revision < Revision.THREE.value:
ScoreApiGenerator.check_on_deploy(context, score)

# owner is set in IconScoreBase.__init__()
context.msg = Message(sender=score.owner)
context.tx = None

self._initialize_score(tx_params.deploy_type, score, params)
self._initialize_score(context, tx_params.deploy_type, score, params)
new_tx_score_mapper[score_address] = score_info
except BaseException as e:
Logger.warning(f'Failed to deploy a SCORE: {score_address}', ICON_DEPLOY_LOG_TAG)
Expand Down Expand Up @@ -302,7 +310,10 @@ def _write_score_to_score_deploy_path(context: 'IconScoreContext',
IconScoreDeployer.deploy_legacy(score_deploy_path, content)

@staticmethod
def _initialize_score(deploy_type: DeployType, score: 'IconScoreBase', params: dict):
def _initialize_score(
context: 'Context',
deploy_type: DeployType,
score: 'IconScoreBase', params: dict):
"""Call on_install() or on_update() of a SCORE
only once when installing or updating it
:param deploy_type: DeployType.INSTALL or DeployType.UPDATE
Expand All @@ -316,5 +327,10 @@ def _initialize_score(deploy_type: DeployType, score: 'IconScoreBase', params: d
else:
raise InvalidParamsException(f'Invalid deployType: {deploy_type}')

TypeConverter.adjust_params_to_method(on_init, params)
if context.revision < Revision.THREE.value:
TypeConverter.adjust_params_to_method(on_init, params)
else:
sig = normalize_signature(on_init)
params = convert_score_parameters(params, sig)

on_init(**params)
1 change: 1 addition & 0 deletions iconservice/icon_constant.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ class Revision(Enum):
SET_IREP_VIA_NETWORK_PROPOSAL = 9
MULTIPLE_UNSTAKE = 9
FIX_COIN_PART_BYTES_ENCODING = 9
VERIFY_INTERNAL_CALL_ARGS = 9

LATEST = 9

Expand Down
27 changes: 16 additions & 11 deletions iconservice/iconscore/icon_score_api_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@
from inspect import signature, Signature, Parameter, isclass, getmembers, isfunction
from typing import Any, Optional, TYPE_CHECKING

from .icon_score_constant import ConstBitFlag, CONST_BIT_FLAG, CONST_INDEXED_ARGS_COUNT, BaseType, \
from .icon_score_constant import ScoreFlag, CONST_INDEXED_ARGS_COUNT, BaseType, \
STR_FALLBACK, STR_ON_INSTALL, STR_ON_UPDATE
from .typing.element import get_score_flag, is_any_score_flag_on
from ..base.address import Address
from ..base.exception import IllegalFormatException, InvalidParamsException
from ..base.type_converter import TypeConverter
Expand Down Expand Up @@ -77,12 +78,12 @@ def __check_on_deploy_function(context: 'IconScoreContext', sig_info: 'Signature
@staticmethod
def __generate_functions(src: list, score_funcs: list) -> None:
for func in score_funcs:
const_bit_flag = getattr(func, CONST_BIT_FLAG, 0)
is_readonly = const_bit_flag & ConstBitFlag.ReadOnly == ConstBitFlag.ReadOnly
is_payable = const_bit_flag & ConstBitFlag.Payable == ConstBitFlag.Payable
score_flag = get_score_flag(func)
is_readonly = bool(score_flag & ScoreFlag.READONLY)
is_payable = bool(score_flag & ScoreFlag.PAYABLE)

try:
if const_bit_flag & ConstBitFlag.External:
if score_flag & ScoreFlag.EXTERNAL:
src.append(ScoreApiGenerator.__generate_normal_function(
func.__name__, is_readonly, is_payable, signature(func)))
elif func.__name__ == ScoreApiGenerator.__API_TYPE_FALLBACK:
Expand Down Expand Up @@ -130,12 +131,16 @@ def __generate_fallback_function(func_name: str, is_payable: bool, sig_info: 'Si

@staticmethod
def __generate_events(src: list, score_funcs: list) -> None:
event_funcs = {func.__name__: signature(func) for func in score_funcs
if getattr(func, CONST_BIT_FLAG, 0) & ConstBitFlag.EventLog}

indexed_args_counts = {func.__name__: getattr(func, CONST_INDEXED_ARGS_COUNT, 0)
for func in score_funcs
if getattr(func, CONST_INDEXED_ARGS_COUNT, 0)}
event_funcs = {
func.__name__: signature(func) for func in score_funcs
if is_any_score_flag_on(func, ScoreFlag.EVENTLOG)
}

indexed_args_counts = {
func.__name__: getattr(func, CONST_INDEXED_ARGS_COUNT, 0)
for func in score_funcs
if getattr(func, CONST_INDEXED_ARGS_COUNT, 0)
}

for func_name, event in event_funcs.items():
index_args_count = indexed_args_counts.get(func_name, 0)
Expand Down
111 changes: 59 additions & 52 deletions iconservice/iconscore/icon_score_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,37 @@
import warnings
from abc import abstractmethod, ABC, ABCMeta
from functools import partial, wraps
from inspect import isfunction, getmembers, signature, Parameter
from typing import TYPE_CHECKING, Callable, Any, List, Tuple
from inspect import isfunction, signature, Parameter
from typing import TYPE_CHECKING, Callable, Any, List, Tuple, Mapping

from .context.context import ContextGetter, ContextContainer
from .icon_score_api_generator import ScoreApiGenerator
from .icon_score_base2 import InterfaceScore, revert, Block
from .icon_score_constant import CONST_INDEXED_ARGS_COUNT, FORMAT_IS_NOT_FUNCTION_OBJECT, CONST_BIT_FLAG, \
ConstBitFlag, FORMAT_DECORATOR_DUPLICATED, FORMAT_IS_NOT_DERIVED_OF_OBJECT, STR_FALLBACK, CONST_CLASS_EXTERNALS, \
CONST_CLASS_PAYABLES, CONST_CLASS_API, T, BaseType
from .icon_score_constant import (
CONST_INDEXED_ARGS_COUNT,
FORMAT_IS_NOT_FUNCTION_OBJECT,
ScoreFlag,
FORMAT_DECORATOR_DUPLICATED,
FORMAT_IS_NOT_DERIVED_OF_OBJECT,
STR_FALLBACK,
CONST_CLASS_API,
CONST_CLASS_ELEMENT_METADATAS,
BaseType,
T,
)
from .icon_score_context_util import IconScoreContextUtil
from .icon_score_event_log import EventLogEmitter
from .icon_score_step import StepType
from .icx import Icx
from .internal_call import InternalCall
from .typing.definition import get_score_api
from .typing.element import (
ScoreElementMetadataContainer,
ScoreElementMetadata,
FunctionMetadata,
set_score_flag_on,
is_any_score_flag_on,
)
from .typing.element import create_score_element_metadatas
from ..base.address import Address
from ..base.address import GOVERNANCE_SCORE_ADDRESS
from ..base.exception import *
Expand All @@ -57,11 +74,10 @@ def interface(func):
if not isfunction(func):
raise IllegalFormatException(FORMAT_IS_NOT_FUNCTION_OBJECT.format(func, cls_name))

if getattr(func, CONST_BIT_FLAG, 0) & ConstBitFlag.Interface:
if is_any_score_flag_on(func, ScoreFlag.INTERFACE):
raise InvalidInterfaceException(FORMAT_DECORATOR_DUPLICATED.format('interface', func_name, cls_name))

bit_flag = getattr(func, CONST_BIT_FLAG, 0) | ConstBitFlag.Interface
setattr(func, CONST_BIT_FLAG, bit_flag)
set_score_flag_on(func, ScoreFlag.INTERFACE)

@wraps(func)
def __wrapper(calling_obj: "InterfaceScore", *args, **kwargs):
Expand Down Expand Up @@ -115,13 +131,11 @@ def eventlog(func=None, *, indexed=0):
if len(parameters) - 1 < indexed:
raise InvalidEventLogException("Index exceeds the number of parameters")

if getattr(func, CONST_BIT_FLAG, 0) & ConstBitFlag.EventLog:
if is_any_score_flag_on(func, ScoreFlag.EVENTLOG):
raise InvalidEventLogException(FORMAT_DECORATOR_DUPLICATED.format('eventlog', func_name, cls_name))

bit_flag = getattr(func, CONST_BIT_FLAG, 0) | ConstBitFlag.EventLog
setattr(func, CONST_BIT_FLAG, bit_flag)
set_score_flag_on(func, ScoreFlag.EVENTLOG)
setattr(func, CONST_INDEXED_ARGS_COUNT, indexed)

event_signature = __retrieve_event_signature(func_name, parameters)

@wraps(func)
Expand Down Expand Up @@ -252,11 +266,13 @@ def external(func=None, *, readonly=False):
if func_name == STR_FALLBACK:
raise InvalidExternalException(f"{func_name} cannot be declared as external")

if getattr(func, CONST_BIT_FLAG, 0) & ConstBitFlag.External:
if is_any_score_flag_on(func, ScoreFlag.EXTERNAL):
raise InvalidExternalException(FORMAT_DECORATOR_DUPLICATED.format('external', func_name, cls_name))

bit_flag = getattr(func, CONST_BIT_FLAG, 0) | ConstBitFlag.External | int(readonly)
setattr(func, CONST_BIT_FLAG, bit_flag)
score_flag = ScoreFlag.EXTERNAL
if readonly:
score_flag |= ScoreFlag.READONLY
set_score_flag_on(func, score_flag)

@wraps(func)
def __wrapper(calling_obj: Any, *args, **kwargs):
Expand All @@ -281,11 +297,16 @@ def payable(func):
if not isfunction(func):
raise IllegalFormatException(FORMAT_IS_NOT_FUNCTION_OBJECT.format(func, cls_name))

if getattr(func, CONST_BIT_FLAG, 0) & ConstBitFlag.Payable:
if is_any_score_flag_on(func, ScoreFlag.PAYABLE):
raise InvalidPayableException(FORMAT_DECORATOR_DUPLICATED.format('payable', func_name, cls_name))

bit_flag = getattr(func, CONST_BIT_FLAG, 0) | ConstBitFlag.Payable
setattr(func, CONST_BIT_FLAG, bit_flag)
flag = ScoreFlag.PAYABLE
if func_name == STR_FALLBACK:
# If a function has payable decorator and its name is "fallback",
# then it is a fallback function
flag |= ScoreFlag.FALLBACK

set_score_flag_on(func, flag)

@wraps(func)
def __wrapper(calling_obj: Any, *args, **kwargs):
Expand Down Expand Up @@ -313,35 +334,18 @@ def on_update(self, **kwargs) -> None:
class IconScoreBaseMeta(ABCMeta):

def __new__(mcs, name, bases, namespace, **kwargs):
if IconScoreObject in bases or name == "IconSystemScoreBase":
return super().__new__(mcs, name, bases, namespace, **kwargs)

cls = super().__new__(mcs, name, bases, namespace, **kwargs)

if IconScoreObject in bases or name == "IconSystemScoreBase":
return cls

if not isinstance(namespace, dict):
raise InvalidParamsException('namespace is not dict!')

custom_funcs = [value for key, value in getmembers(cls, predicate=isfunction)
if not key.startswith('__')]
elements: Mapping[str, ScoreElementMetadata] = create_score_element_metadatas(cls)
setattr(cls, CONST_CLASS_ELEMENT_METADATAS, elements)

external_funcs = {func.__name__: signature(func) for func in custom_funcs
if getattr(func, CONST_BIT_FLAG, 0) & ConstBitFlag.External}
payable_funcs = [func for func in custom_funcs
if getattr(func, CONST_BIT_FLAG, 0) & ConstBitFlag.Payable]

readonly_payables = [func for func in payable_funcs
if getattr(func, CONST_BIT_FLAG, 0) & ConstBitFlag.ReadOnly]

if bool(readonly_payables):
raise IllegalFormatException(f"Payable method cannot be readonly")

if external_funcs:
setattr(cls, CONST_CLASS_EXTERNALS, external_funcs)
if payable_funcs:
payable_funcs = {func.__name__: signature(func) for func in payable_funcs}
setattr(cls, CONST_CLASS_PAYABLES, payable_funcs)

api_list = ScoreApiGenerator.generate(custom_funcs)
api_list = get_score_api(elements.values())
setattr(cls, CONST_CLASS_API, api_list)

return cls
Expand Down Expand Up @@ -382,7 +386,8 @@ def __init__(self, db: 'IconScoreDatabase') -> None:
self.__owner = IconScoreContextUtil.get_owner(self._context, self.__address)
self.__icx = None

if not self.__get_attr_dict(CONST_CLASS_EXTERNALS):
elements: ScoreElementMetadataContainer = self.__get_score_element_metadatas()
if elements.externals == 0:
raise InvalidExternalException('There is no external method in the SCORE')

self.__db.set_observer(self.__create_db_observer())
Expand Down Expand Up @@ -412,8 +417,8 @@ def __validate_external_method(self, func_name: str) -> None:
f"Method not found: {type(self).__name__}.{func_name}")

@classmethod
def __get_attr_dict(cls, attr: str) -> dict:
return getattr(cls, attr, {})
def __get_score_element_metadatas(cls) -> ScoreElementMetadataContainer:
return getattr(cls, CONST_CLASS_ELEMENT_METADATAS)

def __create_db_observer(self) -> 'DatabaseObserver':
return DatabaseObserver(
Expand Down Expand Up @@ -451,17 +456,19 @@ def __check_payable(self, func_name: str):
f"Method not payable: {type(self).__name__}.{func_name}")

def __is_external_method(self, func_name) -> bool:
return func_name in self.__get_attr_dict(CONST_CLASS_EXTERNALS)
elements = self.__get_score_element_metadatas()
func: FunctionMetadata = elements.get(func_name)
return isinstance(func, FunctionMetadata) and func.is_external

def __is_payable_method(self, func_name) -> bool:
return func_name in self.__get_attr_dict(CONST_CLASS_PAYABLES)
elements = self.__get_score_element_metadatas()
func: FunctionMetadata = elements.get(func_name)
return isinstance(func, FunctionMetadata) and func.is_payable

def __is_func_readonly(self, func_name: str) -> bool:
if not self.__is_external_method(func_name):
return False

func = getattr(self, func_name)
return bool(getattr(func, CONST_BIT_FLAG, 0) & ConstBitFlag.ReadOnly)
elements = self.__get_score_element_metadatas()
func: FunctionMetadata = elements.get(func_name)
return isinstance(func, FunctionMetadata) and func.is_readonly

# noinspection PyUnusedLocal
@staticmethod
Expand Down
34 changes: 21 additions & 13 deletions iconservice/iconscore/icon_score_constant.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from enum import IntEnum, unique
from enum import Flag, unique
from typing import TypeVar

from ..base.address import Address
Expand All @@ -23,12 +23,10 @@
# TODO add checking function for list, dict type
BaseType = TypeVar("BaseType", bool, int, str, bytes, list, dict, Address)

CONST_CLASS_EXTERNALS = '__externals'
CONST_CLASS_PAYABLES = '__payables'
CONST_CLASS_INDEXES = '__indexes'
CONST_CLASS_API = '__api'
CONST_CLASS_ELEMENT_METADATAS = '__element_metadatas'

CONST_BIT_FLAG = '__bit_flag'
CONST_SCORE_FLAG = '__score_flag'
CONST_INDEXED_ARGS_COUNT = '__indexed_args_count'

FORMAT_IS_NOT_FUNCTION_OBJECT = "isn't function object: {}, cls: {}"
Expand All @@ -41,14 +39,24 @@

ATTR_SCORE_GET_API = "_IconScoreBase__get_api"
ATTR_SCORE_CALL = "_IconScoreBase__call"
ATTR_SCORE_VALIDATATE_EXTERNAL_METHOD = "_IconScoreBase__validate_external_method"
ATTR_SCORE_VALIDATE_EXTERNAL_METHOD = "_IconScoreBase__validate_external_method"


@unique
class ConstBitFlag(IntEnum):
NonFlag = 0
ReadOnly = 1
External = 2
Payable = 4
EventLog = 8
Interface = 16
class ScoreFlag(Flag):
NONE = 0

# Used for external function
READONLY = 0x01
EXTERNAL = 0x02
PAYABLE = 0x04
FALLBACK = 0x08
FUNC = 0xFF

# Used for eventlog declaration in score
EVENTLOG = 0x100

# Used for interface declaration in score
INTERFACE = 0x10000

ALL = 0xFFFFFF
Loading