Truffle Suite to zestaw narzędzi developerskich dla programistów tworzących projekty w Ethereum (smart contracty). Ten bardzo aktywnie rozwijany projekt, jest aktualnie jednym z najbardziej popularnych frameworków dedykowanych implementacji, testowaniu, a także uruchamianiu zdecentralizowanych aplikacji opartych o Ethereum.

W tym artykule:

  • opiszę narzędzia pakietu Truffle;
  • opsizę i wykorzystam główne narzędzie pakietu Truffle – Truffle (nazwa pakietu i narzędzia jest taka sama);
  • zainicjalizuję przykładowy projekt Pet Shop – zaimplementuję pierwsze funkcjonalności oraz dedykowane testy automatyczne;

Pakiet Truffle (Truffle Suite)

Pakiet Truffle to aktualnie zestaw 3 narzędzi dedykowanych programistom Ethereum. W jego skład wchodzą:

  • Truffle – narzędzie służące tworzeniu struktury projektu. Tworzy strukturę folderów, zarządza zależnościami, umożliwia testowanie automatyczne stworzonego kodu, a także jego uruchamianie.
  • Ganache – osobisty, lokalnie uruchomiony blockchain Ethereum (symulator). Umożliwia ładowanie i uruchamianie implementowanych smart contractów (a także testów).
  • Drizzle – kolekcja bibliotek front-endowych usprawniających implementację webowych interfejsów użytkownika aplikacji dapps.

Narzędzia te nazywane są pakietem, ponieważ kolektywnie wspomagają procesy we wszystkich warstwach implementowanych zdecentralizowanych aplikacji (backend, czyli warstwa kontraktów i frontend). Narzędzia te wymagają instalacji w systemie operacyjnym, co wyklucza ich integrację z webowymi edytorami kodu. Pakiet Truffle (nie licząc Drizzle) funkcjonalnie zbliżony jest do popularnego Remix IDE, czy nowszego Ethereum Studio, które także umożliwiają edycję, kompilację i uruchamianie kontraktów. Zaletą wykorzystania Truffle Suite jest praca z własnym, niezależnym IDE, a także możliwości np. skryptowego automatyzowania procesów (testowania, deploymentu).

Pakiet Truffle to zestaw 3 komponentów dla developerów: narzędzia Truffle, symulatora blockchaina Ethereum –  Ganache oraz zestawu bibliotek frontendowych dla zdecentralizowanych aplikacji – Drizzle.

Narzędzie Truffle – do czego służy?

truffle

Truffle to podstawowe narzędzie pakietu Truffle. Jego funkcjonalności umożliwiają kompleksową realizację projektu opartego o Ethereum: usprawnia implementację smart contractów i strukturyzuje ich kod, umożliwia testowanie oraz uruchamianie kontraktów. Na razie eksperymentalnie, ale już teraz Truffle umożliwia także debugowanie smart contractów. Narzędzie to jest frameworkiem rozwijania smart contractów w Ethereum – posiada zdefiniowany model zarządzania projektem.

Zainicjalizowany projekt przy użyciu tego narzędzia składa się z:

  1. Katalogu contracts – zawiera pliki z kodem źródłowym tworzonych smart contractów.
  2. Katalogu migrations – zawiera pliki definiujących migracje. Migracje to instrukcje kompilacji i uruchamiania (ładowania do sieci) smart contractów. Uławiają kompilację złożonych projektów, skłądających się z wielu plików lub wielu zależności (np. innych smart contractów już istniejących w sieci, które mają być wykorzystane przez tworzone smart contracty).
  3. Katalogu test – zawiera pliki testów automatycznych. Truffle umożliwia tworzenie testów integracyjnych w językach Javascript i Solidity.

Narzędzie Truffle oparte jest o Node.js dlatego zarządzanie zależnościami projektu oparte jest o npm. Konfiguracja projektu, a także migracje wykorzystują język Javascript.

Instalacja narzędzia

Instalacja narzędzia wykorzystuje npm. Instalacja w systemie Linux sprowadza się do uruchomienia w konsoli:

npm install truffle -g

Być może konieczne będzie wywołanie komendy z opcją sudo (w zależności od lokalnej konfiguracji npm).

Ściągnięta paczka zawiera także kompilator Solidity (solc), który jest częścią tego narzędzia – nie ma więc potrzeby osobnej instalacji kompilatora. Po instalacji Truffle będzie dostępny z poziomu powłoki (komenda truffle).

Jak to działa ? Tworzymy projekt Pet Shop

Truffle udostępnia wiele gotowych szkieletów projektów (zwanych boxes, czyli pudełka), które można wykorzystać w zależności od potrzeb. Te gotowe szkielety mogą być pomocne, gdy tworzona przez nas aplikacja posiada zbliżoną funkcjonalność do przykładowego projektu, lub gdy mamy określony stack technologiczny. Takie gotowe pudełko będzie już miało dostarczone zależności, przez co developer nie musi zajmować się początkową inicjalizacją projektu. Lista gotowych pudełek obejmuje aktualnie kilkanaście projektów.

truffle-boxes
Lista przykładowych projektów Truffle, które mogą być użyte jako baza do rozwijania własnego projektu.

Truffle umożliwia “rozpakowanie” szkieletu przykładowego projektu. Instalacja poglądowego projektu polega na wywołaniu komendy:

truffle unbox pet-shop

Komenda pobiera przykładowy projekt Pet Shop. Struktura projektu prezentuje się następująco (otwarty w Intellij Idea, z pluginem Intellij-Solidity):

petshop-project-structure
Szkielet projektu Pet Shop będący przykładowym “pudełkowym” projektem z biblioteki Truffle’a.

Projekt zachowuje opisaną wcześniej strukturę plików. Domyślnie Truffle wspiera tworzenie smart contractów z wykorzystaniem języka Solidity, chociaż możliwe jest pobranie przykładowego projektu opartego o język Vyper.

Aktualnie składa się tylko z 1 pliku kontraktu (Migrations.sol) oraz 1 pliku migracji (1_initial_migration.js). Oba te pliki tworza kolektywnie mechanizm kompilacji i uruchamiania projektu, więc nie należy ich usuwać (na razie także nie modyfikować).

Smart contract

Smart contract realizujący logikę tworzonego Pet Shopu znajduje się w jednym pliku (stworzyłem plik contracts/Adoption.sol). Jego kod jest następujący:

pragma solidity ^0.5.0;

contract Adoption {
    address[16] public adopters;

    function adopt(uint petId) public returns (uint) {
        require(petId >= 0 && petId <=15);
        adopters[petId] = msg.sender;
        return petId;
    }

    function getAdopters() public view returns (address[16] memory) {
        return adopters;
    }
}

Kontrakt zawiera tablicę, która może przechowywać 16 elementów typu address. Zawiera 2 publiczne funkcje: adopt oraz getAdopters.

Funkcja adoptzmienia wewnętrzny stan kontraktu poprzez realizację logiki ‘adopcji’ – przypisanie adresu wywołującego funkcję, do tablicy adopters pod wskazanym indeksem petId. Linia druga skutecznie uniemożliwia przypisanie do elementu tablicy spoza dopuszczalnego zakresu (16 elementów, wiec 0-15). Brak tego zabezpieczenia byłby poważnym błędem. Funkcja zwraca wartość argumentu, którą przyjmuje. Wykorzystany został 8 bitowy typ całkowity bez znaku (najmniejszy typ liczbowy w Solidity) – biorąc pod uwagę przechowywaną wartość, jest to adekwatny typ.

Funkcja getAdoptersoznaczona jest słowem kluczowym view. Funkcje zawierające słowo kluczowe view w definicji wskazują, że nie modyfikują stanu wewnętrznego kontraktu. Innymi słowy, niemożliwe jest przypisanie wartości do jakiegokolwiek pola kontraktu. Próba przeniesienia logiki przypisania pola do tablicy (z funkcji adopt) powoduje następujący błąd kompilacji: TypeError: Function declared as view, but this expression (potentially) modifies the state and thus requires non-payable (the default) or payable.

Kompilacja kontraktu sprowadza się do wywołania komendy:

truffle compile

Wywołanie komendy spowoduje kompilację kontraktu – zostanie wygenerowany folder build zawierający pliki .json, po jednym dla zdefiniowanych kontraktów. Pliki te zawierają m.in. skompilowany bytecode kontraktu, ABI kontraktu (Application Binary Interface), a także dane umożliwiające interakcję z kontraktem z poziomu testu (i informację o wszystkich składowych kontraktu).

Migracja

Kod migracji kontraktu Adoption znajduje się w dodanym przeze mnie pliku migrations/2_deploy_contracts.js. Kod jest następujący:

const Adoption = artifacts.require('Adoption');

module.exports = function(deployer) {
    deployer.deploy(Adoption);
};

Truffle oparty jest o Node.js dlatego możemy wykorzystać słowo kluczowe require, aby zadeklarować stałą Adoption reprezentującą tworzony kontrakt (plik contracts/Adoption.sol). Jak widać, migracja jest prosta, ponieważ kontrakt nie ma żadnych zależności.

Zdefiniowana w ten sposób migracja jest gotowa do uruchomienia (załadowania kontraktu do sieci). Realizuje to komenda:

truffle migrate

Wywołanie komendy wyświetla dane dotyczące wykonanej migracji m.in. hash wykonanej transakcji, adres kontraktu w docelowej sieci, poniesiony koszt gazu. Konfiguracja sieci domyślnie wykrywa uruchomiony symulator Ganache.Konfiguracja sieci testowej (np. Ethereum testnet) zdefiniowana jest w pliku truffle-config.js

Testy

Implementacja testów przy wykorzystaniu Truffle’a obejmuje 2 opcje:

  • testy w Javascript (lub Typescript) – silnikiem testów jest Mocha, biblioteka asercji to Chai,
  • testy w Solidity – mechanizm stworzony przez Truffle’a umożliwia tworzenie testów w postaci smart contractów, które prowadzą bezpośrednią komunikację z testowanymi kontraktami.

Testy Javascriptowe przy użyciu popularnego stacku technologicznie prawdopodobnie będą dobrym wyborem dla programistów, którym znany jest ten język i narzędzia (JS/Mocha/Chai). Uważam ponadto, że zaletą takich testów jest weryfikacja poprawnej konwersji typów (między Solidity a Javascriptem), co też jest wartością dodaną, jako że implementacja dappsów to najczęściej interakcja ze smart contractem z poziomu Javascriptu (web3.js).

Zaletami testowania w Solidity jest minimalistyczne podejście i niski narzut zależności (i złożoności projektu) – Truffle dostarcza proste API do inicjalizowania testów i podstawowych asercji. Ponadto wykorzystanie Solidity na pewno pozwala jeszcze lepiej poznać język.

Testowanie kontraktów w Truffle, niezależnie od wybranej technologii testów – to interakcja bezpośrednio z kontraktem (działającym w blockchainie). Przed uruchomieniem testów kontrakt zostaje załadowany do sieci (np. sieci testowej Ethereum uruchomionej lokalnie, lub do symulatora Ganache). Nie są więc to testy jednostkowe, których uruchomienie spowoduje jedynie przetestowanie “funkcjonalności” w postaci przetworzenia kodu w wyizolowanym lokalnym środowisku. Co prawda testy te uruchamiane w blockchainie, działają w izloacji (każdorazowe uruchomienie testów spowoduje załadowanie testowanego kontraktu do sieci ponownie), to jednak z definicji sama integracja z blockchainem sprawia, że nie są to testy jednostkowe. Testowany kontrakt działa w blockchainie – warto o tym pamiętać testując choćby na środowisku Ethereum Testnet (załadowanie kontraktu to określony koszt “gazu”).

Truffle oferuje możliwość impelementacji testów w Javascript (Mocha/Chai) oraz w Solidity. Testy prowadzą interakcję bezpośrednio z kontraktem załadowanym do blockchaina (np. testowej sieci lub symulatora Ganache).

Poniższy kod to przykładowy kontrakt testowy zaimplementowany w Solidity. Posiada on 2 funkcje testujące kontrakt Adoption:

pragma solidity ^0.5.0;

import "truffle/Assert.sol";
import "truffle/DeployedAddresses.sol";
import "../contracts/Adoption.sol";

contract TestAdoption {

    Adoption adoption = Adoption(DeployedAddresses.Adoption());
    uint expectedPetId = 8;
    address adopterId = address(this);

    function testShouldAdoptCorrectlyAndReturnExpectedAdoptionId() public {
        //when
        uint returnedId = adoption.adopt(expectedPetId);

        //then
        Assert.equal(returnedId, expectedPetId, "Returned adoption ID should match expected id!");
    }

    function testShouldReturnCorrectAdopters() public {
        //when
        address[16] memory adopters = adoption.getAdopters();

        //then
        for (uint i = 0; i< adopters.length; i++) {
            if (i == expectedPetId) {
                Assert.equal(adopters[i], adopterId, "Expected adopter address under adopted pet id!");
            } else {
                Assert.equal(uint(adopters[i]), uint(0), "Expected no adopter!");
            }
        }
    }
}

Jak widać, kod jest hermetyczny, a 2 importy inicjalizują test i dostarczają funkcjonalności asercji. Kontrakty testowe w Truffle muszą zaczynać się od słowa Testz dużej litery, poszczególne funkcje testowe muszą zawierać przedrostek test. Uruchomienie testu (konieczna dzialającą sieć, do której zostanie załadowany kontrakt testowy) sprowadza się do wywołania komendy:

truffle test

Podsumowanie

Rozwijający się ekosystem Ethereum dostarcza coraz to więcej narzędzi wspomagających prace programistów. Truffle to narzędzie pakietu Truffle Suite, będące frameworkiem implementowania, uruchamiania i testowania smart contractów tworzonych w platformie Ethereum. Otwarty kod, cena (Truffle jest darmowym narzędziem), wsparcie społeczności, bogaty zestaw funkcjonalności i stabilne działanie – to kilka z pożądanych cech oprogramowania, które będą brane pod uwagę przez potencjalnych użytkowników. Warto o tym pamiętać przy wyborze, zwłaszcza tworząc rozwiązania oparte o blockchain, gdzie poza znanymi problemami związanymi z narzutem technologicznym, ryzykami i kosztami jakie niesie ze sobą wybór konkretnej technologii, mamy do czynienia z bardziej restrykcyjnym paradygmatem “niezmienności” blockchaina, gdzie koszt popełnienia błędu potrafi być ogromny.

Wykorzystujesz Truffle w swoich projektach? Są alternatywy? Masz opinię temat Truffle’a lub znasz jakieś ciekawostki?
Daj znać w komentarzu. Przemek