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 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:
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.
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.
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
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