Open flantony opened 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.
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.
Here is an simple example of the current state:
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;
}
}
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;
}
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?
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.
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!