Skip to main content

Interfaces

Interface enables the logic to access data and invoke endpoints on other (external) logics during the same interaction. Interface is a description of state fields and endpoints in external logic we're using, so we can list only a few elements of external logic, not the whole structure. We can directly read (observe) external logic's state, but we can't mutate it. If the logic exposes dynamic endpoints that mutate the data, we can invoke such endpoints and mutate the state.

Interface is a generic description of state fields and endpoints we expect the extenal logic provides, to use the interface we need to bind it to a specific logic using logic's Identifier at runtime.

Syntax

Interface has four sections, two for accessing storage of other logic and two for accessing endpoints. We need to define only the sections we want to use (e.g., if we only access one field of logic's state, we only use state logic section and don't use other sections)

interface_syntax.coco
interface InterfaceName: // interface name is used in our code, doesn't need to match other logic's name
state logic: // fields on other logic's state logic we want to access
// only field names we use need to be listed, order is not important,
// but types need to be exact match and names are case-sensitive
Counter U64
owner Person // classes can be accessed, but we need class definition in our logic, too

state actor: // our logic can access data of other logics in actor's context
Balance U256

endpoint: // list of endpoints we're invoking; as with state fields;
// we only need to list the endpoints we invoke, not all endpoints in external logic
dynamic SetSenderBalance(new_balance U256) -> (old_balance U256)
// we need to provide full endpoint signature,
// including access qualifiers (dynamic) if they exist

asset: // list of endpoints, but in an asset logic
// same as `endpoint` section, but separated to explicitly denote
// we're invoking asset endpoints
asset Mint(amount U256) -> (new_amount U256)
full signature, including access qualifiers (asset or dynamic) if they exist

Such interface is bound to a logic with the following syntax:

bind_interface.coco
memory bound_interface = InterfaceName(logic_identifier)
// we bind the interface to an actual logic's identifier
// and create a memory variable we can use to access external elements

_ = bound_interface.SetSenderBalance(new_balance: 10000)
observe external_counter <- bound_interface.Logic.Counter
observe external_balance <- bound_interface.Actor(actor_id).Balance

Interface needs to be bound to a logic before each use, therefore the call needs to be part of every endpoint that uses the external logic. External logic's identifier is therefore usually and argument to the function we call, but if we already know the identifier of the deployed logic (e.g. some well known logic on MOI network), we can also define such identifier as a constant in our logic.

A logic can have multiple interfaces, one for each kind of logic. As interface is bound to a logic in the runtime, the same interface can be also bound to different logics. E.g. the following CounterInterface can be bound to any logic that has Counter U256 in it's state.

counter_interface.coco
interface CounterInterface:
state logic:
Counter U64

Example

In the example in the right column, the logic question accesses another logic, called answer. It uses state actor to access ActorAnswer field on actor's context and endpoint to access an endpoint SetActorAnswer on external logic.

question.coco
coco question

interface Answer:
state actor:
ActorAnswer U64
endpoint:
dynamic SetActorAnswer(actorId Identifier, new_answer U64) -> (set_answer U64)

// sets the actor answer on Sender
endpoint dynamic SetActorAnswer(answerLogicId Identifier, new_answer U64) -> (set_answer U64):
memory answer_iface = Answer(answerLogicId)
set_answer = answer_iface.SetActorAnswer(actorId: Sender, new_answer)

// sets the actor answer on this logic (question)
endpoint dynamic SetMyAnswer(answerLogicId Identifier, new_answer U64):
memory answer_iface = Answer(answerLogicId)
answer_iface.SetActorAnswer(actorId: Identifier(question), new_answer)

// PeekActorAnswer looks directly at the field ActorAnswer, set by `answer` logic on the Sender
endpoint PeekActorAnswer() -> (actor_answer U64):
memory answer_iface = Answer(answerLogicId)
observe actor_answer <- answer_iface.Sender.ActorAnswer // should be the value, set by SetActorAnswer
answer.coco
coco answer

state actor:
ActorAnswer U64

endpoint dynamic SetActorAnswer(actorId Identifier, new_answer U64) -> (set_answer U64):
mutate new_answer -> answer.Actor(actorId).ActorAnswer
observe set_answer <- answer.Actor(actorId).ActorAnswer