O implementare solidă a modelului de proiectare a mașinilor de stat

Scrierea contractelor inteligente este înfricoșătoare. Ei se ocupă de bani reali și uitând de a adăuga un singur cuvânt cheie sau de a greși 2 linii de cod aparent interschimbabile pot duce la pierderea de milioane de dolari. Da, puteți scrie teste și faceți ca profesioniștii să vă verifice codul, dar dacă structura și funcționalitatea contractelor dvs. încep să se complice, atunci există încă o șansă bună ca aceștia să rateze ceva. Nu puteți fi 100% sigur că codul dvs. este în siguranță.

Din fericire, există o multitudine de bune practici, defecte de securitate cunoscute și modele de proiectare, care vă pot ajuta la minimizarea riscurilor. La Token Foundry, una dintre modalitățile prin care ne ajutăm să ne asigurăm vânzările de jetoane este folosind un model de design de programare cunoscut sub numele de „Mașină de stat”.

Dacă programați de ceva vreme, este posibil să fiți deja conștienți de acest tipar. Scopul acestui articol este de a explica:

  • Care este modelul State Machine și când trebuie utilizat
  • O defalcare a mașinii de stat pe care am dezvoltat-o ​​la Token Foundry, cum să o folosiți singur și cum funcționează de fapt
  • Cum acest model poate ajuta la asigurarea securității contractelor dvs.
  • Cum sperăm să ne dezvoltăm în viitor mașina de stat

Care este modelul de proiectare a mașinilor de stat?

Modelul State Machine împarte funcționalitatea unui program într-o serie de „state” diferite. În orice moment dat, programul se află într-o singură stare, în timpul căreia este posibilă doar o funcționalitate specifică stării. Programul poate tranziția între aceste state într-un mod predefinit. De exemplu, un program poate necesita declanșarea manuală a tranzițiilor sau poate trece automat între stări. La Token Foundry definim aceste tranziții automate folosind condiții de pornire a stării, care pot include (dar nu sunt limitate la):

  • O variabilă ia acum o valoare dorită
  • S-a atins un timp specific
  • A apărut un apel sau un eveniment necesar

La intrarea în noua stare, pot apărea modificări de valoare variabilă sau anumite funcționalități pot fi efectuate automat. Funcțiile care trebuie executate la intrarea într-o nouă stare sunt funcții de apelare inversă.

Când trebuie folosit modelul?

Schema de proiectare a mașinii de stat nu este potrivită pentru toate programele sau contractele inteligente. Sistemele care sunt bine adaptate tiparului trebuie să fie ușor defalcate în etape distincte, în care apare un comportament diferit sau este permisă o funcționalitate diferită. Aceste etape ale sistemului sunt reprezentate de state într-o mașină de stare și ar trebui să apară una după alta pe o perioadă de timp.

De exemplu, atunci când dezvăluim informații pe lanț, este comun ca toate părțile să comită hașul informațiilor lor înainte ca toate să dezvăluie valorile reale. Un exemplu în acest sens este votarea - contractul poate avea nevoie de următoarele:

  • Înregistrare - alegătorii se pot înregistra la contract pentru a vota ulterior
  • Angajarea voturilor - s-au angajat opțiunile alese ale alegătorilor
  • Dezvăluirea voturilor - alegătorii își dezvăluie acum votul (care se potrivește cu hash-ul lor)
  • Votarea s-a încheiat - nu este permisă introducerea mai multor votanți

Condițiile de pornire pentru aceste state ar putea declanșa tranziții odată ce un anumit număr de alegători s-au înregistrat sau odată trecut un anumit interval de timp.

O defalcare a mașinii de stat a turnătoriei de jetoane și cum să o folosiți singur

La Token Foundry, am creat câteva contracte inteligente care permit dezvoltatorilor să implementeze cu ușurință un model de mașină de stare liniară (deocamdată). Contractele și testele noastre StateMachine sunt open-source și pot fi găsite pe Token Foundry GitHub pentru ca oricine să citească, să testeze și să îl folosească.

Implementarea noastră permite definirea unui număr arbitrar de state, împreună cu un număr arbitrar de condiții de pornire și funcții de apelare pentru fiecare stat.

Oferim două contracte: StateMachine.sol și TimedStateMachine.sol. Prima dintre acestea este implementarea modelului de bază, iar a doua este o extensie care permite condițiile de pornire bazate pe timestamp pentru stări.

Ideea de bază pentru configurarea mașinii dvs. de stare poate fi împărțită în câțiva pași simpli:

  1. Identificați stările la nivel înalt pe care le va avea mașina dvs.

Acestea sunt definite ca valori constante în contract. De exemplu, într-o vânzare simplă cu simboluri, puteți avea:

bytes32 constant FREEZE = "îngheț";
bytes32 constant IN_PROGRESS = "inProgress";
bytes32 constant ENDED = "încheiat"

Aceste stări trebuie trecute la funcția setStates (bytes32 [] state) pentru a configura mașina de stare. Acest lucru ar trebui să se facă de obicei în constructorul contractului dumneavoastră.

2. Definiți ce funcții vor fi permise în fiecare stare.

Acest lucru este, de asemenea, recomandat să fie executat în constructorul contractului, astfel încât orice funcții neautorizate sunt setate ca atare de la început. De exemplu, continuând exemplul de mai sus, am dori doar ca un contribuabil să poată cumpăra jetoane în timpul stării noastre IN_PROGRESS.

În constructor am pus:
allowFunction (IN_PROGRESS, this.buy.selector);

Aceasta setează funcția de cumpărare ca fiind permisă doar în IN_PROGRESS - dacă mașina de stat este în FREEZE sau ENDED, atunci buy nu poate fi executată. Mai multe despre cum funcționează mai târziu.

3. Definiți condițiile de pornire și funcțiile de apelare ale oricărui stat

Funcțiile de pornire și funcțiile de returnare trebuie definite în contractul dvs. inteligent și trebuie adăugate la stările relevante la contractarea mașinii dvs. de stare.

Condițiile de pornire trebuie să ia următoarea formă, unde bytes32 este ID-ul de stare (de exemplu, FREEZE constant):
exemple de funcțieStartCondition (bytes32) returnări interne (bool) {...}
Callback-urile executate automat la intrarea într-o stare au o formă diferită:
exemplu de funcțieCallback () intern {...}

Acestea sunt setate pentru stările relevante după cum urmează:

addStartCondition (ENDED, hasSaleSoldOut);
addCallback (ENDED, transferMoneyToTeam);

Pentru a simplifica tranzițiile declanșate timestamp, am definit, de asemenea, un TimedStateMachine. Acest contract are o condiție de pornire declanșată cu marcă de timp predefinită și folosește următoarea funcție pentru a permite cu ușurință adăugarea acestor tranziții la o mașină de stare:
function setStateStartTime (bytes32 stateId, uint256 timestamp) intern

Deci, cum funcționează toate acestea?

În contractul nostru StateMachine, avem un modificator numit checkAllowed. Care este definit după cum urmează:

verificator modificator autorizat {
    conditionalTransitions ();
    au nevoie (state [currentStateId] .allowedFunctions [msg.sig]);
    _;
}

Când o funcție este definită folosind modificatorul checkAllowed, funcția condiționalTransitions () este prima dată executată. conditionalTransitions () verifică fiecare dintre condițiile de pornire ale stării ulterioare și dacă oricare dintre acestea sunt adevărate tranziții în starea menționată. Acest proces se repetă până când mașina de stare este în starea actuală corectă. Următoarea linie necesită apoi ca funcția să fie executată în starea curentă.

De exemplu, să zicem că avem o mașină de stare în starea A și că starea B are o condiție de pornire a timpului> = 22:00. Dacă se verifică doar o funcțieInStateA marcată CheckAllowed la 10.05pm, condiționalTransitions va vedea că (time> = 22pm) == true și treceți automat mașina în starea B automat - apelând orice apeluri de apel necesare în acest moment. Se va vedea apoi că mașina este de fapt în starea B și nu execută doarInStateA.

Cum contribuie mașina noastră de stat la îmbunătățirea securității, motivării și gestionării codului

Utilizarea tiparelor de proiectare la programare - inclusiv modelul mașinii de stare - descompune ideile complexe într-o construcție mai simplu înțeleasă. Un sistem reproiectat ca o mașină de stare permite statelor să fie testate și motivate individual, fără a risca interferențe din alte stări și comportamentul acestora.

Construcțiile de mașini de stat permit creșterea clarității fluxului unui sistem. O astfel de simplitate și claritate în contractele inteligente este esențială, mai ales atunci când gestionează sume de bani potențial mari.

Limitări și planuri de viitor

În prezent, implementarea noastră permite doar mașini cu stare liniară (fiecare stare poate avea doar 1 tranziție de ieșire). Acest lucru funcționează bine pentru contractele noastre de vânzare, care au un flux simplu și sunt menite să trăiască doar pentru o perioadă scurtă de timp. Cu toate acestea, dacă sistemul dvs. necesită funcții mai complexe, este posibil ca această implementare să nu fie suficientă. Exemple de astfel de caracteristici sunt inversarea unei tranziții, ramificarea fluxurilor sau efectuarea de cicluri.

În prezent, lucrăm la refactorizarea acestui proiect pentru a sprijini structuri și cicluri non-liniare ale mașinilor, în viitorul nu prea îndepărtat.

Apreciem cu adevărat toate întrebările, întrebările și feedback-urile cu privire la codul pe care îl scriem, așa că vă rugăm să nu ezitați să vă contactați.

Alice Henshaw
Inginer de soliditate @ Token Foundry
www.tokenfoundry.com