Function declaration
Every function mostly is declared via using proof mode in Coq and command Ursus. Let's see how to declare function.
#[attribute1, attribute2..., returns=returnName]
Ursus Definition functionName (arg1:type1) ... (argN:typeN): UExpression returnType ErrorMark.
Where:
Ursusis already mentioned command which inputsDefinition.functionNameis name of declaring function.(arg1:type1) ... (argN:typeN)are arguments, whereargIandtypeIare argument name and argument type of ith argument.- returnName and returnType are "variable name" and type of returning term/object respectively.
attributeIis attribute with the same semanthic as solidity has. Moreover, not only the attributes of solidity functions are available here. Also here is attributeno_body, which means following statement: term of typeUExpression...doesn't exist. So, as mentioned in QuickStart here is 2 goals in proof mode in declaring Ursus function, but with attributeno_bodyonly one goal (IReturnExpression) needs to be declared ("solve").- Finally,
ErrorMarkis actually either true or false term, which expresses if it contains calling require, revert and others "bad" functions here.
After swithing to proof mode two goals are here: UExpression returnType ErrorMark and IReturnExpression.
Conception of ULValue and URValue
ULValue and URValue types of terms that are used in describing function bodies: function callings, assignment and so on. Simply, we can consider ULValue as "reference type" or things that can be assigned a value. We can consider URValue as usual type or things that can't be assigned a value. ULValue can automatically cast to URValue via Coq coercion mechanism. In Ursus we can't use literal itself, we need to wrap it into URValue, so for this goal we can use brackets {...}. For example, "str" of Coq type string now is → {"string"} of type URValue string false. ULValue has type Type -> Type and URValue has type Type -> bool -> Type. Term of type bool has similar semanthic as UExpression... has.
Example is below.
Create term with hole approach
Let's look at basic scenario of describing function (without loops and if statements)
Basic syntax of function body can be introduced via next BNF:
UExpression ::= // simpleUExpression // | // (UExpression ; UExpression) //
i.e function body can be expressed this way:
// simpleUExpression ;
(simpleUExpression ;
(simpleUExpression ;
simpleUExpression)) //
And main idea is using standart tactic refine, so the description will take the following form:
refine // simpleUExpression ; _ // .
refine // simpleUExpression ; _ // .
refine // simpleUExpression ; _ // .
refine // simpleUExpression // .
Where:
- '
_' symbol means, that rest of term is needed to construct. // ... //"brackets" means only some special construction might be here
Firstly, consider next simpleUExpression:
-
declaring "variable"
new 'name : typeName @ "name" := valueWhere:
nameis variable name (notice!nameand "name" must be the same)typeNameis type of this variablevalueis term of typetypeName
-
function
a + b, which has usual semanthic, (aandbare num with the same type, for exampleuint) -
assignment
variable := termWhere:
- variable is "variable", which was declared before asignment
- term is variable or function
For example, consider our first Ursus function:
#[pure, returns=_result]
Ursus Definition addOne (y : uint) : UExpression uint false.
refine // new 'x : uint @ "x" := {1} ; _ //.
refine // y := y + x ; _ //.
refine // _result := y // .
return.
Defined.
Sync.
it will be prettier to rewrite in such way via using goal separator {...}:
#[pure, returns=_result]
Ursus Definition addOne (y : uint) : UExpression uint false.
{
refine // new 'x : uint @ "x" := {1} ; _ //.
refine // y := y + x ; _ //.
refine // _result := y // .
}
return.
Defined.
Sync.
Nice!
Consider custom tactic ::, it works like refine, but here is some features to reduce code. Now we can declare variable with the same semantic, but with new syntax var name : typeName := value
And consider new way of declaring statements via using next notations:
'//' e ';' _ '//'→'//' e'//' e '//'→'//' e '|'
Where 'e' is term of UExpression... type.
And our code become look like this:
#[pure, returns=_result]
Ursus Definition addOne (y : uint) : UExpression uint false.
{
::// var 'x : uint := {1} .
::// y := y + x .
::// _result := y |.
}
return.
Defined.
Sync.
Why should we still use construction new ..., there is one reason var ... doesn't allow to write code in one "piece":
#[pure, returns=_result]
Ursus Definition addOne (y : uint) : UExpression uint false.
refine // new 'x : uint @ "x" := {1} ; y := y + x ; _result := y //.
return.
Defined.
Sync.
How we can use Ursus function below?
Let's consider use case of addOne function.
For example:
::// var new_y: uint := addOne(y);_|.
Where y is URValue uint ... or ULValue uint, which was casted to URValue uint false.
ULValue-URValue
In example above, consider type of each term are used here (or what would say coq proof mode):
y: ULValue uintx: ULValue uint{1}: URValue uint false_result: ULValue uint(coq get this type fromUExpression uint falsereturn type)
Basic construction
Let's look to full simpleUExpression list:
-
assignment
// a := b, whereaandbhave the same type, butaisULValueandbisURValue:ais variable or field of contract or structurebis variable or result of function or field of contract or structure- Operations with assignment. Here is list of available operations with assignment with standart semantic:
x += y,x -= y,x &= y,x |= y,x /= y,x *= y,++ x,x ++,x --,-- x,
Where:
xisULValueandyisURValue -
declaring new varibale
// new 'x : ty @ b := r ; _ |(deprecated),Where:
xis name of variable (important! to use symbol "'")tyis type of variablerris value with typeURValue..._is rest ofUExpressionwhich needs to construct Notice, This way of declaring variable allows to create UExpression withoutrefineor::tactic. After this statement,xwill have typeULValue ty.
-
declaring variables as tuple
// new ( x1 : ty1 , x2 : ty2 ) @ ( xb1 , xb2 ) := r ; _ |"Where:
x1andx2are name of variablety1isty2are type of variablexb1,xb2are strings of namex1andx2respectivelyris value with typety1 ** ty2
-
declaring variable (works only with
::) as tuple// var ( x1 : ty1 , x2 : ty2 ) := r ; _ |Where:
x1andx2are name of variablety1isty2are type of variableris value with typety1 ** ty2
-
declaring variable (works only with
::) as tuple// var x1 : ty1 , x2 : ty2 ; _ |(the same as previous bullet point, but value us default for each type)
After these (point 3,4,5) statement, x1 and x2 will have type ULValue ty1 and ULValue ty2 respectively.
-
calling Ursus function
// function (arg1,...,argN),Where
functionis Ursus function or Ursus Defintion, which was declared above.- arguments
arg1,...,argN, whereargIis result of some function or variable (has typeURValue...)
Complex construction
Consider contruction, which has "body", i.e. consist of another UExpression. We have already worked with it via using // _ ; _ //.
-
First will be if-statement
if x then { y } else { z }xis term with type boolean (URValue boolean mark).markis false or true.yandzhave type the same as type of term, whose part is this if-statement;
For example consider, our old function
#[pure, returns=_result] Ursus Definition addOne (y : uint) : UExpression uint false. { ::// var 'x : uint := {1} . ::// if (x < y) then { ->\> } else { ->\> }. { ::// y := y + x |. } { ::// y := x + y |. } ::// _result := y |. } return. Defined. Sync.Let's consider line
::// if (x < y) then { ->\> } else { ->\> }.before interpreting this line we have one goalUExpression ..., after that we give to this goal expressionif x < y then { ->\> } else { ->\> }and for defining this expression we need to give 2 part of if-statement: true-body and false-body. Notation->\>means{ _ : UExpression ...}(placeholer basicaly, which means that we need define this term below). So after interpretting this constructions we have three goals: true-body, false-body, rest of function. And in the example these "bodies" are: 3. true-body is::// y := y + x |.4. false-body is::// y := x + y |.5. rest of function is::// _result := y |. -
Consider
if x then { y }if-statement. It is the same as the previous point, but here we don't need to define false-body (false-body is void actually here). -
Consider
while b do { f }while-statement with usual semanthic from imperative language,Where
bis term with type boolean (URValue boolean mark).markis false or true.fisUExpression(let's call it while-body)
-
Consider
doWhile b { f }do-while-statement or do-repeat-statement with usual semanthic from imperative language, Wherebis term with type boolean (URValue boolean mark).markis false or true.fisUExpression(let's call it do-while-body)
-
Consider
for (var x : ty := r , b , after ) do { f }for-statement is syntax sugar of this expression:// var x : ty := r ; while ( b ) do { f ; after }- So, we can do something with
xin bodyf
- So, we can do something with
-
Consider
for ( [ k , v ] in m ) do { f }for-each-expression, which has next semanthic in case of typem:- Let
mof typemapping keyType valueType, so on body expression variablekandvare aviable infbody,
Where
kandvarekeyandvaluetype, respectively. It iterates for each key and value of this mapping 2. Letmof typeA[],Where
kandvareuintandAtype, respectively. It iterates for each index and value of arrayA[]3. So, we can do something withkandv, they have typesULValue keyandULValue valuerespectively in bodyf. - Let
-
Consider
for ( [ k , v ] in m ; cond ) do { f }for-each-expression with condition has the same semantic as the previus one, but iteration can break if conidtioncondisn't satisfied.- So, we can do something with
kandv, they have typesULValue keyandULValue valuerespectively in bodyf.
- So, we can do something with
-
Consider
for ( v in m ) do { f }yet another version of for-each-expression, which has next semanthic in case of typem: -
Let
mof typemapping keyType valueType, so on body expression variablevis aviable infbody,
Where v has value type. It iterates for each value of this mapping
2. Let m of type A[],
Where ```v``` has ```A``` type. It iterates for each value of array ```A[]```
- So, we can do something with
v, it has typeULValue valuerespectively in bodyf. - Consider
for ( v in m ; cond ) do { f }for-each-expression with condition has the same semantic as the previus one, but iteration can break if conidtioncondisn't satisfied.- So, we can do something with
v, it has typeULValue valuerespectively in bodyf.
- So, we can do something with
How to model modifiers?
Ursus can model modifiers as simple void function (let's call it as modifier_example), i.e. term with the next type: type1 -> ... -> typeN -> UExpression PhantomType false or type1 -> ...-> typeN -> UExpression PhantomType true.
And modifier invocation will use like this in Ursus function:
Ursus Definition some_function:UExpression PhantomType false.
::// modifier_example() ;_|.
{
...
}
return.
Defined.