Interfaces and Messages
Interfaces enable type-safe inter-contract communication in Ursus. This section covers defining interfaces, declaring message recipients, and sending messages.
Overview
Interfaces define the external API of a contract - the functions that other contracts can call.
Messages are the mechanism for calling functions on other contracts.
Key benefits:
- Type safety - Compile-time checking of message sends
- Documentation - Explicit contract dependencies
- Verification - Formal verification of inter-contract calls
- Code generation - Automatic interface bindings
Defining Interfaces
Interfaces are defined in separate files or at the beginning of contract files.
Interface File Structure
Require Import UrsusEnvironment.Solidity.current.Environment.
Interfaces.
SetUrsusOptions.
Unset Ursus Extraction.
MakeInterface Class IInterfaceName :=
{
method1 : type1 -> type2 -> UExpression returnType flag ;
method2 : type3 -> UExpression returnType flag ;
...
}.
EndInterfaces.
Interface Syntax
Basic structure:
MakeInterface Class IInterfaceName :=
{
methodName : arg1Type -> arg2Type -> ... -> UExpression returnType flag
}.
Components:
IInterfaceName- Interface name (conventionally starts withI)methodName- Function nameargTypes- Curried argument typesreturnType- Return typeflag- Panic flag (trueorfalse)
Example: ERC20 Interface
Interfaces.
SetUrsusOptions.
Unset Ursus Extraction.
MakeInterface Class IERC20 :=
{
#[view, public]
balanceOf : address -> UExpression uint256 false ;
#[external]
transfer : address -> uint256 -> UExpression boolean true ;
#[external]
approve : address -> uint256 -> UExpression boolean true ;
#[external]
transferFrom : address -> address -> uint256 -> UExpression boolean true
}.
EndInterfaces.
Method Attributes
Methods can have attributes:
#[view, public] (* Read-only public method *)
#[external] (* External method *)
#[override, external] (* Override parent method *)
#[external, payable] (* Payable external method *)
Declaring Message Recipients
The Sends To declaration specifies which interfaces a contract can send messages to.
Syntax
Sends To Interface1 Interface2 Interface3 ;
Examples
Multiple interfaces:
Sends To IERC20 ITokenReceiver IOwnable ;
Single interface:
Sends To IERC20 ;
No external calls:
Sends To ;
Purpose
The Sends To declaration:
- Enables type checking - Only declared interfaces can be called
- Documents dependencies - Makes external calls explicit
- Supports verification - Enables formal verification of calls
- Generates bindings - Creates interface code in target language
Sending Messages
Messages are sent using the send notation.
Syntax
::// send Interface.Method(arg1, arg2, ...)
=> target_address
with message_params .
Components
Interface.Method- Interface and method name(arg1, arg2, ...)- Method argumentstarget_address- Recipient contract addressmessage_params- Message parameters (value, flags, etc.)
Basic Example
::// send IERC20.Transfer(from, to, amount)
=> token_address
with {InternalMessageParamsLRecord} [$ {2} ⇒ {Message_ι_flag} $] .
Message Parameters
Message parameters control how the message is sent:
{InternalMessageParamsLRecord} [$
{value} ⇒ {Message_ι_value} ;
{flags} ⇒ {Message_ι_flag} ;
{bounce} ⇒ {Message_ι_bounce}
$]
Common parameters:
Message_ι_value- Amount of tokens to sendMessage_ι_flag- Message flagsMessage_ι_bounce- Bounce on error
Complete Example
Interface Definition (IERC20.v)
Require Import UrsusEnvironment.Solidity.current.Environment.
Interfaces.
SetUrsusOptions.
Unset Ursus Extraction.
MakeInterface Class IERC20 :=
{
#[view, public]
balanceOf : address -> UExpression uint256 false ;
#[external]
transfer : address -> uint256 -> UExpression boolean true ;
#[view, public]
totalSupply : UExpression uint256 false
}.
EndInterfaces.
Contract Using Interface
Require Import UrsusEnvironment.Solidity.current.Environment.
Require Import UrsusEnvironment.Solidity.current.LocalGenerator.
Require Import IERC20.
Module TokenWrapper.
#[translation = on]
#[language = solidity]
Contract TokenWrapper ;
Sends To IERC20 ;
Types ;
Constants ;
Record Contract := {
token: address;
total_wrapped: uint256
}.
SetUrsusOptions.
UseLocal Definition _ := [uint256; address; boolean].
#[external, returns=_result]
Ursus Definition wrapTokens (amount: uint256): UExpression boolean true.
{
::// send IERC20.transfer(this->address, amount)
=> token
with {InternalMessageParamsLRecord} [$ {0} ⇒ {Message_ι_value} $] .
::// total_wrapped := total_wrapped + amount .
::// _result := @true |.
}
return.
Defined.
Sync.
EndContract Implements.
End TokenWrapper.
Advanced Patterns
Callback Interfaces
Define interfaces for callbacks:
MakeInterface Class ITokenReceiver :=
{
#[external]
onTokenReceived : address -> uint256 -> UExpression PhantomType true
}.
Usage:
::// send ITokenReceiver.onTokenReceived(sender, amount)
=> recipient
with {InternalMessageParamsLRecord} [$ {0} ⇒ {Message_ι_value} $] .
Multiple Interface Calls
Sends To IERC20 IOwnable ITokenReceiver ;
Ursus Definition complexOperation: UExpression PhantomType true.
{
::// send IERC20.transfer(recipient, amount)
=> token_address
with {InternalMessageParamsLRecord} [$ {0} ⇒ {Message_ι_value} $] .
::// send ITokenReceiver.onTokenReceived(msg->sender, amount)
=> recipient
with {InternalMessageParamsLRecord} [$ {0} ⇒ {Message_ι_value} $] .
::// send IOwnable.transferOwnership(new_owner)
=> ownable_contract
with {InternalMessageParamsLRecord} [$ {0} ⇒ {Message_ι_value} $] |.
}
return.
Defined.
Conditional Message Sending
::// if should_notify then { ->> } | .
{
::// send ITokenReceiver.onTokenReceived(sender, amount)
=> recipient
with {InternalMessageParamsLRecord} [$ {0} ⇒ {Message_ι_value} $] |.
}
Best Practices
1. Use Descriptive Interface Names
Good:
MakeInterface Class IERC20 := { ... }.
MakeInterface Class ITokenVesting := { ... }.
MakeInterface Class IOwnable := { ... }.
Avoid:
MakeInterface Class I1 := { ... }.
MakeInterface Class MyInterface := { ... }.
2. Group Related Methods
Good:
MakeInterface Class IERC20 :=
{
(* View methods *)
#[view, public]
balanceOf : address -> UExpression uint256 false ;
#[view, public]
totalSupply : UExpression uint256 false ;
(* State-changing methods *)
#[external]
transfer : address -> uint256 -> UExpression boolean true ;
#[external]
approve : address -> uint256 -> UExpression boolean true
}.
3. Document Panic Flags
MakeInterface Class IToken :=
{
(* Can panic: requires sufficient balance *)
transfer : address -> uint256 -> UExpression boolean true ;
(* Cannot panic: pure view function *)
#[view, public]
balanceOf : address -> UExpression uint256 false
}.
4. Declare All Dependencies
Good:
Sends To IERC20 IOwnable ITokenReceiver ;
Avoid:
Sends To ;
(* Missing interfaces - will cause compilation errors *)
Common Patterns
Token Transfer
::// send IERC20.transfer(recipient, amount)
=> token_address
with {InternalMessageParamsLRecord} [$ {0} ⇒ {Message_ι_value} $] .
Ownership Transfer
::// send IOwnable.transferOwnership(new_owner)
=> contract_address
with {InternalMessageParamsLRecord} [$ {0} ⇒ {Message_ι_value} $] .
Callback Notification
::// send ICallback.onComplete(result)
=> callback_address
with {InternalMessageParamsLRecord} [$ {0} ⇒ {Message_ι_value} $] .
See Also
- Contract Header - Sends To declaration
- Contract Structure - Overall organization
- File Interface - Interface file structure
- Functions - Implementing interface methods
- Notations - Message send notation details