Web3, Smart Contract, Metamask e Web Application

di Andrea Zani, in Blockchain,

Smart contract e Web3 client side

Dopo i primi due post della serie, uno dedicato alla Blockchain e uno agli Smart Contract con interoperabilità con il C#, ecco questo post dedicato ancora agli Smart Contract con application client side con i Wallet per la gestione dell'autenticazione. Però prima ancora un po' di teoria sugli Smart Contract.

Funzioni Payable e problemi vari

Nello scorso post ho scritto qualche dettaglio sulla programmazione degli Smart Contract in Ethereum grazie al linguaggio Solidity. Naturalmente non voleva essere una guida esaustiva, anche perché la documentazione ufficiale è ben scritta (inoltre è disponibile molto materiale scritto da appassionati e programmatori professionisti). Ho cercato di descrivere anche i vari attributi dei metodi di Solidity, come la visibilità (external, public, etc...) e il modo come dichiarare i parametri di input e output. E' il momento di utilizzare il parametro payable.

Uno Smart Contract è equiparabile ad un Wallet. In esso è possibile depositare cryptovaluta così come è possibile con esso versare cryptovaluta in un Wallet o in un altro Smart Contract. Utilizzando sempre come editor Remix di Ehetereum, nel tab Deploy & run transaction, nella sezione dove si specifica il Wallet di test da utilizzare e il Gas Limit, è presente anche una textbox Value e un dropdownlist dove sono presenti le varie unità di misura dell'Ethereum: Wei, Gwei, Finney e Ether (sempre nello scorso post maggiori info a riguardo):

In questa textbox Value è possibile inserire quanta cryptovaluta si vuole immettere dal Wallet selezionato (0x5b3...edd4c) nello Smart Contract quando viene richiamato uno dei suoi metodi. Provando a inserire un valore differente da zero e provando a richiamare un metodo a caso come il seguente (preso dagli esempi presenti in Remix):

function store(uint256 num) external {
    number = num;
}

Al momento della creazione della transazione si ottiene un errore generico:

L'errore è dovuto dal metodo che non supporta la ricezione di cryptovaluta. L'attributo payable è utilizzato proprio per questo. Creo un metodo di questo tipo minimale:

function payme() payable external {}

Notare l'assenza di parametri di input o output che sono del tutto opzionali - un metodo di tipo payable è del tutto identico a qualsiasi altro metodo. Una volta fatto il deploy appare un nuovo pulsante - payme - dal colore differente da quelli visti finora: rosso.

Ora inserendo nella textbox un quantitativo di cryptovaluta e cliccato su payme, dal Wallet selezionato sarà decurtato il quantitativo selezionato che sarà depositato nello Smart Contract. Sì, ma dove? E come lo gestisco? Creo un altra metodo:

function getMoney() external view returns(uint) {
    return address(this).balance;
}

Rifatto il Deploy e depositato con payme altra cryptocurrency, cliccato su getMoney sarà visualizzato il quantitativo depositato nello Smart Contract. Se volessi controllare il quantitativo di valuta al momento dell'inserimento dovrei scrivere nel metodo payme:

function payme() payable external {
    require(msg.value == 1 ether, "I accept only 1 ETH!");
}

msg è una global variable che contiene queste informazioni:

Grazie a require nella funzione qui sopra viene accettato solo un Ether, ogni qualsiasi altro quantitativo sarà scartato con un errore.

Ipotizzando di avere più metodi e di volere eseguire questo controllo (o altri) Solidity mette a disposizione i Modifier. Essi vengono definiti nel codice come se fossero funzioni:

modifier Only1Ether {
    require(msg.value == 1 ether, "I accept only 1 ETH!");
    _;
    // Accept only 1 ETH
}

Ora il metodo payme:

function payme() payable Only1Ether external {}

E farà il controllo come prima. Il vantaggio è che ora posso utilizzare il Modifier in tutte le funzioni del mio contract. Ottimo, ma attenzione, i Modifier non eseguono nessuna ottimizzazione e funzionano al pari della macro in C++. La funzione payme al compilatore giungerà scritta così:

function payme() payable external {
    require(msg.value == 1 ether, "I accept only 1 ETH!");
    // Accept only 1 ETH
}

In modifier i caratteri _; sono usati come segnaposto dove sarà inserito il codice della funzione in cui è stato aggiunto l'attributo Only1Ether.

Per muovere cryptovaluta dallo Smart Contract ad un altro Wallet o Smart Contract si deve scrivere una funzione come la seguente:

function payto(address to) external {
    (bool sent, ) = payable(to).call{value: address(this).balance}("");
    require(sent, "Failed to send Ether to external address");
}

Non ho dovuto inserire l'attributo payable nella definizione della funzione, ma ho dovuto fare il boxing di Address al tipo payable altrimenti avrei avuto un errore.

Spesso ci si troverà nella situazione di dover abilitare alcune funzioni solo a determinati utenti, come al creatore dello Smart Contract. Il trucco è usare il constructor che, come detto nello scorso post, viene eseguito solo alla creazione del Contract e usare un Modifier per i metodi che voglio che siano accessibili solo dall'owner del Contract:

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.0;

contract TestContractOwner {
    address private _owner;
    string private _message;

    modifier OnlyOwner {
        require(msg.sender == _owner, "Only Owner!");
        _;
    }

    constructor() {
        _owner = msg.sender;
    }

    function setMessage(string calldata message) external OnlyOwner {
        _message = message;
    }

    function getMessage() external view returns(string memory) {
        return _message;
    }
}

Nel constructor salvo l'Address dell'utente che ha creato il contratto, quindi con il Modifier verifico che il richiedente della funzione per la modifica del messaggio sia uguale a quell'utente. Notare che avrei potuto scrivere anche lo stesso codice senza costruttore:

contract TestContractOwner {
    address private _owner = msg.sender;
    string private _message;
...

Quanto spiegato ora torna utile quando uno Smart Contract raccoglie cryptovaluta e si vuole che, manualmente, qualcuno possa cederla a terzi. Un metodo accessibile a chiunque sarebbe ovviamente un disastro. Nell'esempio che inserirò più avanti creerò una semplicissima lotteria dove gli utenti possono acquistare con lo Smart Contract un numero e ad un certo punto il creatore assegnerà, a sua discrezione, la cryptovaluta raccolta ad un unico vincitore:

function pickWinner(uint8 winNumber) public onlyOwner {
    address to = payable(take_address_from_mapping);
    (bool sent, ) = to.call{value: _balance}("");
    ...
}

Su questo punto mi soffermo un attimo. Come ho scritto più volte uno Smart Contract è come un Wallet, e può raccogliere cryptovaluta. Ma cosa succede se la funzione qui sopra, nel momento di prendere l'Address a cui inviare la vincita, incontrasse un bug non visto in fase di sviluppo e non potesse inviare la cryptovaluta al legittimo futuro proprietario? La faccio semplice: quella cryptovaluta è persa per sempre. Anche se venisse trovato il bug è corretto, l'invio del codice non modificherebbe lo Smart Contract esistente (si ricorderà che in una Blockchain nulla può essere modificato) e ne sarà creato uno nuovo con i suoi oggetti non condivisi con la versione buggata. Per evitare questo problema è bene creare una funzione apposita che richiami il selfdestruct dello Smart Contract:

function kill() external onlyOwner {
    selfdestruct(payable(_owner));
}

Questa funziona sarà utilizzabile solo dall'owner del contratto e invierà tutta la cryptovaluta salvata all'interno dello Smart Contract al suo Wallet (e poi potrà agire di conseguenza: versamento della vincita, etc...).

Wallet

Finora si è solo nominato il Wallet. E' ora di installare un gestore dei Wallet sulla macchina, o per meglio dire, come addons nel browser (Chrome o Firefox) e vedere come collegarlo alla Blockchain i test locale creata con Ganache. Innanzitutto si deve avviare questo tool - io ho scelto Metamask perché è il più famoso, ma ce ne sono molti altri. Dopo averlo installato apre nel browser la schermata:

Se non dovesse apparire in automatico è sufficiente premere CTRL + Alt + M. Avendo già tutto in Ganache seleziono la prima voce perché voglio utilizzare uno dei Wallet creati da quel programma. Quando è richiesta la Secret Recovery Phrase inserisco la lista delle dodici parole visibili in Ganache nalla pagina degli Accounts. Per proteggere solo l'accesso all'installazione corrente di Metamask viene chiesta anche una password che però, è importante saperlo, non è legata in alcun modo all'account importato o creato (ogni istanza di Metamask su altri dispositivi possono avere altre password). Alla fine, riaprendo Metamask con la combinazione di tasti o cliccando sulla sua icona, apparirà la window:

Nel mio caso in alto avrò questo Address per la mia Wallet:

0xDe945A653609b311c5fCb264343DdA66858139B0

Che è lo stesso che trovo in Ganache:

In Metamask, nella parte superiore della window, è presente la mainnet di Ethereum, che nel mio caso è errato. Ora è il momento di collegare Ganache. Cliccando sul nome di questa rete sarà visibile un'opzione che rimanda alle impostazioni dove si possono attivare le reti di test. Attivata l'opzione:

E' già presente una Localhost ma ha la porta sbagliata (Ganache di default, come si è visto anche nello scorso post usa la porta 7545 di default). In linea teorica si può modificare quella rete esistente, nella pratica è impossibile (nella versione attuale qualsiasi modifica alla porta visualizza un errore e rimette il valore della porta precedente). Poco male, cliccando su Add Network si aprirà una pagina nel browser dove inserire la nuova rete, ma prima di farlo, cliccando su Networks, è sufficiente cancellare la rete esistente e quindi creare la nuova:

In Chain ID inserire il valore numerico 1337 e in Symbol qualsiasi stringa. Cliccato su Save e tornato alla window principale di Metamask, se tutto ha funzionato, vedrò la network di Ganache collegata con il mio Account e il valore corretto di Ether:

E' possibile inserire anche gli altri Address di Ganache (o di qualsiasi altro Address di cui si conosce la Private Key). Nel tab degli Accounts è sufficiente cliccare sul simbolo della piccola chiave sulla sinistra per l'account che si vuole importare in Metamask, e sarà visualizzata la Private Key. Copiata è possibile importarla in Metamask. Cliccando sul simbolo in alto a sinistra appare la lista degli account già presenti, quindi cliccando sulla voce Import Account, si può inserire la Private Key e ora l'account sarà gestibile da Metamask.

Vorrei sprecare ancora qualche parola sull'Address. Come scritto nel primo post di questa serie questa stringa alfanumerica è un prodotto della Public Key che è a sua volta creata da una Private Key casuale. L'Address qui utilizzato non è collegato in modo univoco al network di test di Ganache. Io posso usare lo stesso Address su qualsiasi rete, anche sulla mainnet di Ethereum. Non si deve registrare né renderne conto a nessuno della sua esistenza: chi ha la chiave privata ne é il proprietario. In Metamask ora posso selezionare anche la mainnet:

E potrei utilizzare lo stesso Address per ricevere cryptovaluta da altri Address o da qualsiasi Exchange preposto a questo scopo. Al primo avvio di Metamask si può creare anche un nuovo Wallet che, in automatico, creerà una nuova Private Key -> Public Key -> Address (e saranno fornite anche la lista di dodici parole da utilizzare per recupare la Private Key) che potrò utilizzare anch'esso in qualsiasi network sia quello principale mainnet che qualsiasi di test. Esagerando, posso creare decine di Wallet, ma anche centinaia, e solo io che posseggo la Private Key di ognuno le potrò utilizzare attivamente. C'è qualche vantaggio nell'avere decine di Wallet? No.

L'utilità dei tool come Metamask è solo nella visualizzazione delle informazioni sul Wallet? No, questi strumenti vengono utilizzati come parte attiva per autorizzare le transazioni e l'invio di Cryptovaluta, inoltre possono essere utilizzati anche per eseguire l'Authentication nelle web application. Sul primo punto tornerò a breve.

Lotteria in Solidity

Ora ho tutto l'ambiente pronto. Prima di passare all'applicazione di test che utilizzerà Metamask è il momento di scrivere lo Smart Contract dell'esempio. Scopiazzando dagli numerosi esempi presenti in rete, creerò una piccola lotteria dove gli utenti potranno scommettere sull'uscita di un numero pseudo casuale da 1 a 9. Ecco tutto il codice:

// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0 <0.9.0;

contract LotteryBlog {

    event EnterNumberEvent(uint8 number, address from, uint value);
    event PickWinnerEvent(uint8 winNumber, address to, uint value);
    event KillContractEvent();

    struct Player {
        uint8 Number;
        bool Exist;
    }

    struct Number {
        address Address;
        bool Exist;
    }

    mapping(address => Player) private _players;
    mapping(uint8 => Number) private _numbers;
    
    address private _owner = msg.sender;
    uint8[] private _onlyNumbers;
    uint private _balance = 0;
    bool private _isActive = true;

    modifier onlyOwner {
      require(msg.sender == _owner, "Only owner");
      _;
   }

   modifier isActive {
       require(_isActive, "Lottery is closed");
       _;
   }

    function getBalance() external view onlyOwner isActive returns(uint) {
        return _balance;
    }

    function enter(uint8 number) external isActive payable {
        require(msg.value == 1 ether, "Accepted only 1 ETH");
        require(number > 0 && number < 10, "Number can be from 1 to 9");
        require(!_players[msg.sender].Exist, "Player already has a number");
        require(!_numbers[number].Exist, "Number already assigned");

        _players[msg.sender] = Player(number, true);
        _numbers[number] = Number(msg.sender, true);
        _onlyNumbers.push(number);
        _balance += msg.value;

        emit EnterNumberEvent(number, msg.sender, msg.value);
    }

    function getNumbersUsed() external view returns (uint8[] memory) {
        return _onlyNumbers;
    }

    function pickWinner(uint8 winNumber) public onlyOwner isActive {
        require(_numbers[winNumber].Exist, "winNumber do not exist in array");
        address to = payable(_numbers[winNumber].Address);
        (bool sent, ) = to.call{value: _balance}("");
        require(sent, "Failed to send Ether to the winner");
        emit PickWinnerEvent(winNumber, to, _balance);
        _balance = 0;
        _isActive = false;
    }

    function kill() external onlyOwner {
        selfdestruct(payable(_owner));
        emit KillContractEvent();
    }
}

Ad inizio codice ho definito due struct dove memorizzerò gli Address e i numeri scelti dai partecipanti alla lotteria. Le regole sono semplici vedendo i require nella funzione Enter: un utente può scommettere solo su un numero, non si può scegliere un numero già selezionato da un altro utente, è accettata come scommessa solo il valore di un Ether. Superate questi require, le informazioni vengono salvate e solo l'owner dello Smart Contract potrà, quando vorrà, con la funzione pickWinner, inserire il numero vincente che assegnerà in automatico la vincita all'utente che ha inserito il numero corretto - anche qui c'è il require che obbliga la selezione di un numero che effettivamente è stato inserito nelle scommesse. Inoltre è presente la funzione getBalance - utilizzabile solo dall'owner - per avere il valore attualmente salvato come valuta all'interno del contratto, e getNumbersUsed che visualizza i numeri attualmente inseriti. Per sicurezza è presente anche la funzione kill spiegata precedentemente.

Questo Smart Contract è utilizzabile dall'IDE di Remix. Utile per eseguire i vari test, ma lo scopo finale è utilizzarlo da una applicazione.

Accesso via web agli Smart Contract con il Wallet

Si ritorna a parlare di Web3 e il fantomatico futuro che propone per il futuro di Internet. Ora creo una web application che dovrà utilizzare lo Smart Contract mostrato. Il tutto sarà fatto con una semplice pagina Html e del codice Javascript, che si interfaccerà con lo Smart Contract e con il Wallet Metamask. Niente di complesso. Il tutto è abbastanza simile a quanto esposto nello scorso post riguardante il C#.

Inizio creando un web server minimale in Net6 che, con tre righe di codice, fa quanto mi serve:

var app = WebApplication.CreateBuilder(args).Build();
app.UseFileServer();
app.Run();

Ora nella directory wwwroot sarà possibile inserire il contenuto - pagine html e quant'altro - che potrò richiamare via browser. Ecco la pagina principale in html:

<html>
<head>
    <title>Lottery Blog</title>
    <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/web3@latest/dist/web3.min.js"></script>
    <link rel="stylesheet" href="css/lottery.css">
</head>

<body>
    <h1>Lottery Blog</h1>
    <h4>
        <span>Wallet Address: </span><span id="walletAddress">-</span>
    </h4>

    <div id="grid">
        <div class="lottery-board" id="grid9">
        </div>
    </div>

    <script type="text/javascript" src="js/lottery.js"></script>
</body>

</html>

Viene inclusa la libreria web3.js che farà tutto il lavoro e il mio file Javascript lottery.js:

var account = null;
var contract = null;

const ABI = [{...}];
const ADDRESS = '0x2890....63783D0';

(async () => {
    try {
        if (window.ethereum) {
            window.web3 = new Web3(window.ethereum);

            window.ethereum.on('accountsChanged', function (accounts) {
                checkAndDraw();
            });

            window.ethereum.on('networkChanged', function (networkId) {
                checkAndDraw();
            });

            await checkAndDraw();
        }
        else {
            alert("Metamask is not active");
        }
    }
    catch (err) {
        alert("Generic error: " + err);
    }
})();

In ADDRESS ho inserito l'Address del contratto e in ABI il JSON contenente i metadata dello Smart Contract (dove sono inserite le informazioni sulle funzioni presenti così come i parametri di input e output utilizzati - informazione reperibile dall'IDE di Remix come spiegato nello scorso blog). Controllando l'oggetto window.ethereum verifico che sia installato Metamask (o altro Wallet), in caso negativo viene visualizzato l'errore, altrimenti viene aperta la window di questo Wallet Manager dove si potrà selezionare il Wallet desiderato nella rete di Ethereum da utilizzare. Per questa demo controllo che il network connesso sia private (gestendo lo switch di Metamask grazie agli eventi accountChanged e networkChanged) altrimenti visualizzo un errore generico. Così come fanno altri siti che gestiscono i Wallet, questo mi consente di verificare se utilizzo la mainnet o una qualsiasi rete di test e informare l'utente. Inoltre questa modalità di accesso con Metamask apre un nuovo tipo di autenticazione basato proprio sul Wallet e network desiderato (così come viene fatto da altre web application come OpenSea). La funzione che ho creato getWalletAddress prende proprio le informazioni riguardanti il Wallet selezionato dall'utente:

async function getWalletAddress() {
    await window.ethereum.enable();
    const accounts = await window.ethereum.request({method: 'eth_requestAccounts'});
    account = accounts[0];
    document.getElementById('walletAddress').textContent = account;
    });
}

Questo Address potrebbe essere utilizzato per creare il cookie di autenticazione da usare nella web application. Interessante ma non approfondirò in questo post anche perché non mi sono ancora noti alcuni punti di questo tipo di autenticazione come la sicurezza...

La funzione getContractInfo istanzia l'oggetto in Javascript per poter utilizzare lo Smart Contract:

function getContractInfo() {
    contract = new web3.eth.Contract(ABI, ADDRESS);
    console.log(contract);
}

E' giunto il momento di chiamare il metodo dello Smart Contract per avere la lista dei numeri già selezionati: getNumebersInserted. Questo è il codice:

async function makeGrid() {
    if (contract) {
        var numbers = await contract.methods.getNumbersUsed().call();
        console.log(`Numbers from blockchain: ${numbers} ${numbers.length}`);

        var grid = document.getElementById('grid9');
        for (let i = 1; i < 10; i ++) {
            // create grid
        }
    }
}

Con una riga la libreria Web3 fa tutto il lavoro. Se si ricorda il codice in C# avevo dovuto specificare anche la rete Ethereum da utilizzare, in questo caso ho potuto scavalcare questo passaggio perché sarà il collegamento creato da Metamask a passare tutti i parametri alla libreria Web3 in Javascript.

Nell'immagine qui sopra ho già inserito l'acquisto di quattro numeri fatta dall'IDE di Remix. Ora acquisterò un numero dalla pagina web. Sempre nel precedente post, avevo usato molte parole sulla pericolosità di utilizzare funzioni in C# che avviavano nello Smart Contract delle transazioni, perché era necessario l'inclusione della Private Key. In questo caso il passagio è fattibile con il minimo dei rischi, perché il tutto sarà gestito da Metamask e nel nostro codice non saranno mai passate informazioni come la Private Key. Sempre da Javascript ora voglio fare in modo che l'utente possa cliccare nel Box con il numero su cui scommettere:

function buyTicket(number, event) {
    console.log(`buyTicket: ${number}`);

    (async ()=>{
        try
        {
            const reply = await contract.methods.enter(number.toString()).send({from: account, value: 1000000000000000000});
            console.log(reply);
        }
        catch(err) {
            // show error message
        }
    })();

    ...
}

Questo codice richiama la funzione Enter passando il numero su cui l'utente ha cliccato e il valore di un Ether come value in Wei. Prima di eseguirlo si aprirà la window di Metamask dove appariranno tutte le informazioni della transazione che l'utente potrà accettare o meno:

In questo modo ho ovviato al problema citato nello scorso post. La chiave privata non è disponibile e utilizzabile dalla mia applicazione e il tutto avviene con molta più sicurezza con tutte le informazioni necessarie per la transazione.

Ma pure tu quanto mi costi?

E' tempo di controllare il costo di tutto questo con il prezzo del gas a 50 Gwei:

  • Per l'inserimento dello Smart Contract nella Blockchain: 526.383 unità di gas (¤77.68).
  • Per l'inserimento dei primo numero da parte dell'owner: 136.835 (¤20.19)
  • Inserimento secondo numero: 85.635 (¤12.63).
  • Inserimento terzo numero: 85.635 (¤12.63).
  • Assegnazione della vincita: 37.912 (¤5.59).

Ovviamente il costo non dipende dal quantitativo di cryptovaluta coinvolta nella transazione, lo stesso costo lo avrei avuto anche se avessi potuto scommettere un Gwei).

Nelle ottimizzazioni è consigliato l'uso degli oggetti base per la lunghezza delle variabili. Il tipo base è sempre a 256 bit e ogni altra dimensione dell'oggetto viene prima convertito. Io nel mio codice ho inserito quello che io ritenevo il tipo ideale per quell'utilizzo: per esempio per la memorizzazione dei numeri vincenti ho utilizzato il tipo uint8 (8 bit). E se avessi utilizzato la dimensione consigliata? Ecco lo stesso codice utilizzando, dove è possibile, oggetti a 256bit:

// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0 <0.9.0;

contract LotteryBlog {

    event EnterNumberEvent(uint number, address from, uint value);
    event PickWinnerEvent(uint winNumber, address to, uint value);
    event KillContractEvent();

    struct Player {
        uint Number;
        bool Exist;
    }

    struct Number {
        address Address;
        bool Exist;
    }

    mapping(address => Player) private _players;
    mapping(uint => Number) private _numbers;
    
    address private _owner = msg.sender;
    uint[] private _onlyNumbers;
    uint private _balance = 0;
    bool private _isActive = true;

    modifier onlyOwner {
      require(msg.sender == _owner, "Only owner");
      _;
   }

   modifier isActive {
       require(_isActive, "Lottery is closed");
       _;
   }

    function getBalance() external view onlyOwner isActive returns(uint) {
        return _balance;
    }

    function enter(uint number) external isActive payable {
        require(msg.value == 1 ether, "Accepted only 1 ETH");
        require(number > 0 && number < 10, "Number can be from 1 to 9");
        require(!_players[msg.sender].Exist, "Player already has a number");
        require(!_numbers[number].Exist, "Number already assigned");

        _players[msg.sender] = Player(number, true);
        _numbers[number] = Number(msg.sender, true);
        _onlyNumbers.push(number);
        _balance += msg.value;

        emit EnterNumberEvent(number, msg.sender, msg.value);
    }

    function getNumbersUsed() external view returns (uint[] memory) {
        return _onlyNumbers;
    }

    function pickWinner(uint winNumber) public onlyOwner isActive {
        require(_numbers[winNumber].Exist, "winNumber do not exist in array");
        address to = payable(_numbers[winNumber].Address);
        (bool sent, ) = to.call{value: _balance}("");
        require(sent, "Failed to send Ether to the winner");
        emit PickWinnerEvent(winNumber, to, _balance);
        _balance = 0;
        _isActive = false;
    }

    function kill() external onlyOwner {
        selfdestruct(payable(_owner));
        emit KillContractEvent();
    }
}

Il funzionamento è del tutto simile a quello precedente, ma quanto consuma?

  • Creazione: 502.016 unità di Gas.
  • Inserimento primo numero: 158.802.
  • Inserimento secondo numero: 124.602.
  • Inserimento terzo numero: 124.602.
  • Assegnazione della vincita: 37.865.

Dalla versione otto di Solidity il problema del maggiore consumo di Gas per la conversione all'oggetto base per le variabili è stato ridotto ma non del tutto risolto come si nota nella creazione del contratto. Il resto delle operazioni sono addirittura più onerose (come ci si dovrebbe aspettare) usando il tipo di dato a 256 bit. E questo a cosa porta? L'ottimizzazione è certosina e deve sempre essere testata più e più volte per trovare il risparmio massimo. Sinceramente io non ho trovato nessuna regola fissa nell'ultima major release di Solidity.

Conclusioni

E si conclude qua la terza parte dedicata alle Blockchain, Smart Contract e divertimenti simili. Manca solo un post: quello dedicato agli NFT. E' solo fuffa? O c'è realmente qualcosa di utile dietro a essi che non sia speculazione? Premetto: ovviamente non si troverà la risposta a questi quesiti e, soprattutto, nessun consiglio su come speculare su di essi. Sarà solo una mia disanima tecnica.

Qui il codice sorgente.

Commenti

Visualizza/aggiungi commenti

| Condividi su: Twitter, Facebook, LinkedIn

Per inserire un commento, devi avere un account.

Fai il login e torna a questa pagina, oppure registrati alla nostra community.

Nella stessa categoria
I più letti del mese