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

Commit

Permalink
IS-1112: Allow list and dict as score parameter (#470)
Browse files Browse the repository at this point in the history
* List and TypedDict are available for score parameter
* Refactor IconScoreBaseMeta and IconScoreBase
* Verify internal call arguments
  • Loading branch information
goldworm-icon authored Jul 6, 2020
1 parent 7ec7412 commit 3d00bd7
Show file tree
Hide file tree
Showing 75 changed files with 2,271 additions and 477 deletions.
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

0 comments on commit 3d00bd7

Please sign in to comment.