Syntax

Type hints

Type hinting is highly recommended for the input parameters and return value. When querying SCORE’s APIs, API specification is generated based on its type hints. If type hints are not given, only function names will return.

Example)

@external(readonly=True)
def func1(arg1: int, arg2: str) -> int:
    return 100

Possible data types for function parameters are int, str, bytes, bool, Address. List and Dict type parameters are not supported yet.

Returning types can be int, str, bytes, bool, Address, List, Dict.

Exception handling

When you handle exceptions in your contract, it is recommended to use revert function rather than using an exception inherited from IconServiceBaseException.

IconScoreBase (The highest parent class)

Every classes must inherit IconScoreBase. Contracts not derived from IconScoreBase can not be deployed.

__init__

This is a python init function. This function is called when the contract is loaded at each node.

Member variables can be declared here, however, Declaring member variables which not managed by states is prohibited. Utilities of DB such as VarDB, DictDB, ArrayDB can be declared here as a member variable as follows.

Example)

self._total_supply = VarDB(self._TOTAL_SUPPLY, db, value_type=int)
self._decimals = VarDB(self._DECIMALS, db, value_type=int)
self._balances = DictDB(self._BALANCES, db, value_type=int)

Also, parent’s init function must be called as follows.

Example)

super().__init__(db)

on_install

This function is called when the contract is deployed for the first time, and will not be called again on contract update or deletion afterward. This is the place where you initialize the state DB.

VarDB, DictDB, ArrayDB

VarDB, DictDB, ArrayDB are utility classes wrapping the state DB. A key can be a number or characters, and value_type can be int, str, Address, and bytes. If the key does not exist, these classes return 0 when value_type is int, return “” when str, return None when the value_type is Address or bytes. VarDB can be used to store simple key-value state, and DictDB behaves more like python dict. DictDB does not maintain order, whereas ArrayDB, which supports length and iterator, maintains order.

VarDB(‘key’, ‘target db’, ‘return type’)

Example) Setting theloop for the key name on the state DB:

VarDB('name', db, value_type=str).set('theloop')

Example) Getting value by the key name:

name = VarDB('name', db, value_type=str).get()
print(name) ## 'theloop'

DictDB(‘key’, ‘target db’, ‘return type’, ‘dict depth (default is 1)’)

Example 1) One-depth dict (test_dict1[‘key’]):

test_dict1 = DictDB('test_dict1', db, value_type=int)
test_dict1['key'] = 1 ## set
print(test_dict1['key']) ## get 1

print(test_dict1['nonexistence_key']) # prints 0 (key does not exist and value_type=int)

Example 2) Two-depth dict (test_dict2[‘key1’][‘key2’]):

test_dict2 = DictDB('test_dict2', db, value_type=str, depth=2)
test_dict2['key1']['key2'] = 'a' ## set
print(test_dict2['key1']['key2']) ## get 'a'

print(test_dict2['key1']['nonexistent_key']) # prints "" (key does not exist and value_type=str)

If the depth is more than 2, dict[key] returns new DictDB. Attempting to set a value to the wrong depth in the DictDB will raise an exception.

Example 3)

test_dict3 = DictDB('test_dict3', db, value_type=int, depth=3)
test_dict3['key1']['key2']['key3'] = 1 ## ok
test_dict3['key1']['key2'] = 1 ## raise mismatch exception

test_dict2 = test_dict3['key']['key2']
test_dict2['key1'] = 1 ## ok

ArrayDB(‘key’, ‘target db’, ‘return type’)

ArrayDB supports one dimensional array only. ArrayDB supports put, get, and pop. Does not support insert (adding elements in the middle of array).

test_array = ArrayDB('test_array', db, value_type=int)
test_array.put(0)
test_array.put(1)
test_array.put(2)
test_array.put(3)
print(len(test_array)) ## prints 4
print(test_array.pop()) ## prints 3
test_array[0] = 0 ## ok
# test_array[100] = 1 ## error
for e in test_array: ## ok
    print(e)
print(test_array[-1]) ## ok
# print(test_array[-100]) ## error

external decorator (@external)

Functions decorated with @external can be called from outside the contract. These functions are registered on the exportable API list. Any attempt to call a non-external function from outside the contract will fail. If a function is decorated with ‘readonly’ parameters, i.e., @external(readonly=True), the function will have read-only access to the state DB. This is similar to view keyword in Solidity. If the read-only external function is also decorated with @payable, the function call will fail. Duplicate declaration of @external will raise IconScoreException on import time.

payable decorator (@payable)

Only functions with @payable decorator are permitted to transfer icx coins. Transferring 0 icx is acceptable. If msg.value (icx) is passed to non-payable function, the call will fail.

eventlog decorator (@eventlog)

Functions with @eventlog decorator will include logs in its TxResult as ‘eventlogs’. It is recommended to declare a function without implementation body. Even if the function has a body, it does not be executed. When declaring a function, type hinting is a must. Without type hinting, transaction will fail. The default value for the parameter can be set.

If indexed parameter is set in the decorator, designated number of parameters in the order of declaration will be indexed and included in the Bloom filter. At most 3 parameters can be indexed, And index can’t exceed the number of parameters(will raise an error). Indexed parameters and non-indexed parameters are separately stored in TxResult.

Example)

# Declaration
@eventlog
def FundTransfer1(self, _backer: Address, _amount: int, _isContribution: bool): pass

@eventlog(indexed=1) # The first param (backer) will be indexed
def FundTransfer2(self, _backer: Address, _amount: int, _isContribution: bool): pass

# Execution
self.FundTransfer1(self.msg.sender, amount, True)
self.FundTransfer2(self.msg.sender, amount, True)

Possible data types for function parameters are primitive types (int, str, bytes, bool, Address). Array, Dictionary and None type parameter is not supported.

fallback

fallback function can not be decorated with @external. (i.e., fallback function is not allowed to be called by external contract or user.) This fallback function is executed whenever the contract receives plain icx coins without data. If the fallback function is not decorated with @payable, the icx coin transfers to the contract will fail.

InterfaceScore

InterfaceScore is an interface class used to invoke other SCORE’s function. This interface should be used instead of legacy ‘call’ function. Usage syntax is as follows.

class TokenInterface(InterfaceScore):
    @interface
    def transfer(self, addr_to: Address, value: int) -> bool: pass

If other SCORE has the function that has the same signature as defined here with @interface decorator, then that function can be invoked via InterfaceScore class object. Like @eventlog decorator, it is recommended to declare a function without implementation body. If there is a function body, it will be simply ignored.

Example) You need to get an InterfaceScore object by using IconScoreBase’s built-in function create_interface_score('score address', 'interface class'). Using the object, you can invoke other SCORE’s external function as if it is a local function call.

token_score = self.create_interface_score(self._addr_token_score.get(), TokenInterface)
token_score.transfer(self.msg.sender, value)