Skip to main content

Function Call Context

Proxy objects provide generic access capabilities to the data on the host. It is now time to introduce the gateway to the host that allows you to access the functionality that the host sandbox interface provides. We call this gateway the function call context , and it is provided as a predefined parameter to the smart contract function. In fact, you can distinguish two separate flavors of smart contract functions in the IOTA Smart Contracts:

  • Func, which allows full mutable access to the smart contract state, and always results in a state update.
  • View, which allows only limited, immutable access to the smart contract state, and therefore does not result in a state update. Views can be used to efficiently query the current state of the smart contract.

To support this function type distinction, Func and View functions each receive a separate, different function call context. Only the functionality that is necessary for their implementation can be accessed through their respective contexts, ScFuncContext and ScViewContext. ScViewContext provides a limited, immutable subset of the full functionality provided by ScFuncContext. By having separate context types, compile-time type-checking can be used to enforce their usage constraints.

An important part of setting up a smart contract is defining exactly which Funcs and Views are available and informing the host about them. The host will have to dispatch the function calls to the corresponding smart contract code. To that end, the smart contract Wasm code will expose an externally callable function named on_load that will be called by the host upon initial loading of the smart contract code. The on_load function must provide the host with the list of Funcs and Views, and specific identifiers that can be used to invoke them. It uses a special temporary function context named ScExports. That context can be used to provide the host with a function, type, name, and identifier for each Func and View that can be called in the smart contract.

When the host need to call a smart contract function, it has to do it by invoking a second externally callable function named on_call. The host passes the identifier for the smart contract Func or View that needs to be invoked. The client Wasm code will then use this identifier to set up the corresponding function context and call the function. Note that there are no other parameters necessary because the function can subsequently access any other function-related data through its context object.

Here is a (simplified) example from the dividend example smart contract that showcases some features of WasmLib:

func OnLoad() {
exports := wasmlib.NewScExports()
exports.AddFunc("divide", funcDivide)
exports.AddFunc("init", funcInit)
exports.AddFunc("member", funcMember)
exports.AddFunc("setOwner", funcSetOwner)
exports.AddView("getFactor", viewGetFactor)
exports.AddView("getOwner", viewGetOwner)
}

The on_load() function first creates the required ScExports context, and then proceeds to define four Funcs named divide, init, member, and setOwner. It does this by calling the add_func() method of the ScExports context. Next it defines two Views named getFactor and getOwner by calling the addView() method of the ScExports context. The second parameter to these methods is the actual smart contract function associated with the name specified. These methods will also automatically assign a unique identifier to the function and then send everything to the host.

In its simplest form this is all that is necessary to initialize a smart contract. To finalize this example, here is what the skeleton function implementations for the above smart contract definition would look like:

func funcDivide(ctx wasmlib.ScFuncContext) {
ctx.Log("dividend.funcDivide")
}

func funcInit(ctx wasmlib.ScFuncContext) {
ctx.Log("dividend.funcInit")
}

func funcMember(ctx wasmlib.ScFuncContext) {
ctx.Log("dividend.funcMember")
}

func funcSetOwner(ctx wasmlib.ScFuncContext) {
ctx.Log("dividend.funcSetOwner")
}

func viewGetFactor(ctx wasmlib.ScViewContext) {
ctx.Log("dividend.viewGetFactor")
}

func viewGetOwner(ctx wasmlib.ScViewContext) {
ctx.Log("dividend.viewGetOwner")
}

As you can see, each function is provided with a context parameter, which is conventionally named ctx. Notice that the four Funcs are passed an ScFuncContext, whereas the two Views receive an ScViewContext.

This example also showcases an important feature of the contexts: the log() method. This can be used to log human-readable text to the host's log output. Logging text is the only way to add tracing to a smart contract, because it does not have any I/O capabilities other than what the host provides. There is a second logging method, called trace(), that can be used to provide extra debug information to the host's log output. The trace output can be selectively turned on and off at the host.

In the next section we will introduce the schema tool that simplifies smart contract programming a lot.