itemisCREATE / solidity-ide

Solidity plugin for Eclipse
Eclipse Public License 1.0
86 stars 25 forks source link

Codegenerator - Layout of generated Solidity code #151

Open flantony opened 6 years ago

flantony commented 6 years ago

The simple goal of the statechart integration is to empower non-programmers to create solidity smart contracts.

In order to achieve this we will integrate the open source tool YAKINDU Statechart Tools , which provides a graphical editor to create finite state machines and generate code from it. The tool has proven its abilities in education, various industries and even in safety critical environments where human lives are at stake.

diagramtocode

We started implementing a code generator to automatically create Solidity code from a model.

The goal for the code generator is to produce code that is "optimal" in regards to:

While being experienced in building tools and code generators, we are relatively new to Solidity and therefore need your help!

Any hint to pitfalls, best practices, tips & tricks, reviews, discussions or even code contributions are more than welcome!

andreasmuelder commented 6 years ago

There are basically 3 ways to implement a state machine in code:

YAKINDU Statecharts uses the switch case approach since it is a good compromise between readability and execution speed. But maybe we should check which of the implementation pattern is best in relation to gas consumption.

flantony commented 6 years ago

The solidity documentation contains an example that uses modifiers to implement state transition logic. For the minimalistic example given this makes sense, but i am not sure if the statemachiene gets bigger and has additional guard conditions.

flantony commented 6 years ago

Here is an simple example of the current state:

simplesctexample

This will currently (beta not ready to work with) lead to following code:

pragma solidity ^0.4.18;
contract SimpleExample {
     enum States {
        SimpleExample_main_region_StateA,
        SimpleExample_main_region_StateB,
        SimpleExample_main_region_StateC
    }

    Events private lastEvent;

    enum Events {
        A,
        B,
        C
    }

    // This is the current state.
    States public activeState = States.SimpleExample_main_region_StateA;

    // Owner of the contract
    address private owner; 
    // Owner of the contract
    uint private lastInteraction; 

    // Internal scope

    modifier react() {
        _;
        if(activeState == States.SimpleExample_main_region_StateA){
            if ((Events.B == lastEvent)) {
                // TODO implement state reactions
            }
        }
        if(activeState == States.SimpleExample_main_region_StateB){
            if ((Events.C == lastEvent)) {
                // TODO implement state reactions
            }
        }
        if(activeState == States.SimpleExample_main_region_StateC){
            if ((Events.A == lastEvent)) {
                // TODO implement state reactions
            }
        }
    }

    // constructor
    function SimpleExampleStatemachine()public {
        owner = msg.sender;
    }

    // default function
    function() public payable {}            

    // Scope
    function A() public react {
        lastInteraction = block.timestamp;
        lastEvent = Events.A;
    }   

    function B() public react {
        lastInteraction = block.timestamp;
        lastEvent = Events.B;
    }   

    function C() public react {
        lastInteraction = block.timestamp;
        lastEvent = Events.C;
    }   

}
andreasmuelder commented 6 years ago

Global variables aka the contract's state is stored on the blockchain and a very expensive operation. Is there a reason to persist the lastEvent or could it be passed in as argument to the modifier like this:

modifier react(Events lastEvent){
    _;
    if(activeState == States.SimpleExample_main_region_StateA){
        if ((Events.B == lastEvent)) {
            // TODO implement state reactions
        }}
    if(activeState == States.SimpleExample_main_region_StateB){
        if ((Events.C == lastEvent)) {
            // TODO implement state reactions
        }}
    if(activeState == States.SimpleExample_main_region_StateC){
        if ((Events.A == lastEvent)) {
            // TODO implement state reactions
        }}
}
function A() public react(Events.A){
    lastInteraction = block.timestamp;
}
function B() public react(Events.B){
    lastInteraction = block.timestamp;
}
function C() public react(Events.C){
    lastInteraction = block.timestamp;
}
flantony commented 6 years ago

This looks reasonable to me. I am wondering how to implement guads when sticking to this pattern. I could imagine to just negate the guard condition and throw an exception in case it's true. Then generate the reactions (exit, entry) into the functions?