Writing SCORE¶
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 receive
incoming ICX coins. Transferring 0 icx is acceptable. If ICX coins
(msg.value) are passed to a non-payable function, that transaction
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)