icondev

> hyperconnect the world _

ICON is a scalable smart contract enabled blockchain platform with a long-term goal of interoperability between enterprise and public blockchains. Our goal is to Hyperconnect the World, and by combining groundbreaking technology, a strong community, and relentless growth strategies we believe this goal is reachable.

Get Started     Subscribe to our mailing list

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 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)

Built-in functions

create_interface_score('score address', 'interface class') -> interface class instance

This function returns an object, through which you have an access to the designated SCORE's external functions.

Built-in properties

msg : Holds information of the account who called the SCORE

  • msg.sender :
    Address of the account who called this function.
    If other contact called this function, msg.sender points to the caller contract's address.
  • msg.value :
    Amount of icx that the sender attempts to transfer to the current SCORE.

tx : Transaction info

  • tx.origin : The account who created the transaction.
  • tx.index : Transaction index.
  • tx.hash : Transaction hash.
  • tx.timestamp : Transaction creation time.
  • tx.nonce : (optional) random value.

icx : An object used to transfer icx coin

  • icx.transfer(addr_to(address), amount(integer)) -> bool
    Transfers designated amount of icx coin to addr_to.
    If exception occurs during execution, the exception will be escalated.
    Returns True if coin transfer succeeds.

  • icx.send(addr_to(address), amount(integer)) -> bool
    Sends designated amount of icx coin to addr_to.
    Basic behavior is same as transfer, the difference is that exception is caught inside the function.
    Returns True when coin transfer succeeded, False when failed.

db : db instance used to access state DB

address : SCORE address

owner : Address of the account who deployed the contract

block_height : Current block height

now : Wrapping function of block.timestamp.

API functions

revert(message: str) -> None

Developer can force a revert exception.
If the exception is thrown, all the changes in the state DB in current transaction will be rolled back.

sha3_256(data: bytes) -> bytes

Computes hash using the input data

json_dumps(obj: Any, **kwargs) -> str

Converts a python object obj to a JSON string

json_loads(src: str, **kwargs) -> str

Parses a JSON string src and converts to a python object

Common classes

Address

  • prefix: AddressPrefix.EOA(0) or AddressPrefix.CONTRACT(1)
  • body: 20-byte address body part
  • is_contract: Whether the address is SCORE
  • to_bytes(self) -> bytes:
    Returns data as bytes from the address object
  • from_string(address: str) -> Address:
    A static method creates an address object from given 42-char string address
  • from_data(prefix: AddressPrefix, data: bytes) -> Address:
    A static method creates an address object(type of prefix) using given bytes data

Writing SCORE


Suggested Edits are limited on API Reference Pages

You can only suggest edits to Markdown body content, but not to the API spec.