Skip to content

Use of Inline Action in Smart Contracts

In smart contracts, one can also initiate an action, known as an inline action. It is important to note that actions are asynchronous, which means that the corresponding contract code of the inline action will be called only after the entire code has been executed. If the called contract does not define the corresponding action or there is no deployed contract in the account, the call will have no effect but no exception will be thrown either. These empty inline actions can be used as on-chain logs for querying by applications.

Here is the complete code of the Action class in action.codon:

@packer
class Action(object):
    account: Name
    name: Name
    authorization: List[PermissionLevel]
    data: bytes

    def __init__(self, account: Name, name: Name, data: bytes=bytes()):
        self.account = account
        self.name = name
        self.authorization = [PermissionLevel(account, n'active')]
        self.data = data

    def __init__(self, account: Name, name: Name, permission_account: Name, data: bytes=bytes()):
        self.account = account
        self.name = name
        self.authorization = [PermissionLevel(permission_account, n'active')]
        self.data = data

    def __init__(self, account: Name, name: Name, permission_account: Name, permission_name: Name, data: bytes=bytes()):
        self.account = account
        self.name = name
        self.authorization = [PermissionLevel(permission_account, permission_name)]
        self.data = data

    def __init__(self, account: Name, name: Name, authorization: List[PermissionLevel], data: bytes=bytes()):
        self.account = account
        self.name = name
        self.authorization = authorization
        self.data = data

    def send(self):
        raw = pack(self)
        send_inline(raw.ptr, u32(raw.len))

    def send(self, data: T, T: type):
        self.data = pack(data)
        raw = pack(self)
        send_inline(raw.ptr, u32(raw.len))

The class has three __init__ methods. Please use as per your requirements. The most commonly used should be the following initialization function:

def __init__(self, account: Name, name: Name, data: bytes=bytes())

This function defaults to the active permission of the account.

The following initialization function specifies the account of the permission, and also defaults to the active permission of the account:

def __init__(self, account: Name, name: Name, permission_account: Name, data: bytes=bytes())

If other permissions are used, the following initialization function can be used:

def __init__(self, account: Name, name: Name, permission_account: Name, permission_name: Name, data: bytes=bytes()):

If multiple permissions are used by the account, then use this initialization function.

def __init__(self, account: Name, name: Name, authorization: List[PermissionLevel], data: bytes=bytes()):

Example:

# action_example.codon
from packer import pack
from chain.action import Action, PermissionLevel
from chain.contract import Contract

@packer
class Person:
    name: str
    height: u64
    def __init__(self, name: str, height: u64):
        self.name = name
        self.height = height

@contract(main=True)
class MyContract(Contract):

    def __init__(self)
        super().__init__()

    @action('test')
    def test(self):
        a = Action(n'hello', n'test2')
        print('++++send test2 action')
        a.send("1 alice")

        a = Action(n'hello', n'test2', n'hello')
        print('++++send test2 action')
        a.send("2 alice")

        a = Action(n'hello', n'test2', n'hello', n'active')
        print('++++send test2 action')
        a.send("3 alice")

        a = Action(n'hello', n'test2', [PermissionLevel(n"hello", n"active")])
        print('++++send test2 action')
        a.send("4 alice")

        a = Action(n'hello', n'test3')
        print('++++send test3 action')
        a.send(Person("alice", 175u64))
        return

    @action('test2')
    def test2(self, name: str):
        print('++++=name:', name)

    @action('test3')
    def test3(self, name: str, height: u64):
        print('++++=name:', name, 'height:', height)

Test code:

def test_action():
    t = init_test('action_example')
    ret = t.push_action('hello', 'test', {}, {'hello': 'active'})
    t.produce_block()
    logger.info("++++++++++%s", ret['elapsed'])

Compile:

python-contract build action_example.codon

Run the test:

ipyeos -m pytest -s -x test.py -k test_action

Output:

debug 2023-03-28T12:35:48.175 thread-0  apply_context.cpp:30          print_debug          ] 
[(hello,test)->hello]: CONSOLE OUTPUT BEGIN =====================
++++send test2 action
++++send test2 action
++++send test2 action
++++send test2 action
++++send test3 action

[(hello,test)->hello]: CONSOLE OUTPUT END   =====================
debug 2023-03-28T12:35:48.175 thread-0  apply_context.cpp:30          print_debug          ] 
[(hello,test2)->hello]: CONSOLE OUTPUT BEGIN =====================
++++=name: 1 alice

[(hello,test2)->hello]: CONSOLE OUTPUT END   =====================
debug 2023-03-28T12:35:48.175 thread-0  apply_context.cpp:30          print_debug          ] 
[(hello,test2)->hello]: CONSOLE OUTPUT BEGIN =====================
++++=name: 2 alice

[(hello,test2)->hello]: CONSOLE OUTPUT END   =====================
debug 2023-03-28T12:35:48.175 thread-0  apply_context.cpp:30          print_debug          ] 
[(hello,test2)->hello]: CONSOLE OUTPUT BEGIN =====================
++++=name: 3 alice

[(hello,test2)->hello]: CONSOLE OUTPUT END   =====================
debug 2023-03-28T12:35:48.175 thread-0  apply_context.cpp:30          print_debug          ] 
[(hello,test2)->hello]: CONSOLE OUTPUT BEGIN =====================
++++=name: 4 alice

[(hello,test2)->hello]: CONSOLE OUTPUT END   =====================
debug 2023-03-28T12:35:48.175 thread-0  apply_context.cpp:30          print_debug          ] 
[(hello,test3)->hello]: CONSOLE OUTPUT BEGIN =====================
++++=name: alice height: 175

[(hello,test3)->hello]: CONSOLE OUTPUT END   =====================
debug 2023-03-28T12:35:48.177 thread-0  controller.cpp:2444           clear_expired_input_ ] removed 0 expired transactions of the 50 input dedup list, pending block time 2018-06-01T12:00:04.000

As you can see, it first calls the test action specified in the Transaction, and then calls the test2 Action. However, the test2 action is not specified in the Transaction, but is initiated in the smart contract. In addition, it demonstrates how to send an action with multiple parameters through test3.

It should be noted that in order to call inline action in the contract, you need to add the eosio.code virtual permission to the active permission of the account. In the test code, the eosio.code virtual permission is added to the active permission through the following function.

def update_auth(chain, account):
    a = {
        "account": account,
        "permission": "active",
        "parent": "owner",
        "auth": {
            "threshold": 1,
            "keys": [
                {
                    "key": 'EOS6AjF6hvF7GSuSd4sCgfPKq5uWaXvGM2aQtEUCwmEHygQaqxBSV',
                    "weight": 1
                }
            ],
            "accounts": [{"permission":{"actor":account,"permission": 'eosio.code'}, "weight":1}],
            "waits": []
        }
    }
    chain.push_action('eosio', 'updateauth', a, {account:'active'})

Comments