Sempre più persone sentono parlare di ICO e quindi si immaginano di ottenere soldi facili con un wallet online aperto come un cesto di basketball dove gli investitori non vedono l’ora di schiacciar dentro palloni foderati di ether, bitcoin, litecoin e tutti gli altri coin della famiglia.
Il tutto perché si tratta di smart contract, sono lì apposta. Ora a parte la difficoltà di metter su una vera ICO, difficoltà che aumenta ogni giorno perché ogni giorno l’asticella si sposta verso l’alto e la competizione è in crescita esponenziale, ci sono anche aspetti meramente tecnici su cui riflettere.
ICO a parte ogni smart contract è sempre un pezzo di “programmable money”. Anzi, nel momento in cui viene messo in moto sulla blockchain è un pezzo di “programmed money” e come ben sanno tutti quelli che hanno scritto un hello world non è possibile scrivere un software senza metterci dentro, involontariamente si spera, un bug.
“In accordance with Unix philosophy, Perl gives you enough rope to hang yourself” –Larry Wall
Un bug che colpisce un programma già è una cosa seccante, se colpisce il nostro denaro diventa un disastro. A differenza di un normale sistema in produzione che una volta individuato il bug si può fixare e riavviare – e pazienza per il downtime – quando succede ad uno smart contract in realtà ripristinare lo stato corretto di esecuzione non è scontato. Si può sostituire un contract con uno corretto, ma non è detto che si possano recuperare gli ether. Infatti non si tratta di recuperare dati, si tratta di recuperare gli effetti di transazioni irrevocabili. Per questa ragione Ethereum sembra un progetto pazzesco. Tanto pazzesco che però piace a molti e sta diventando la piattaforma di riferimento per mettere bug nei soldi, ops … per creare smart contract.
Se nel caso del software normale l’audit del codice sembra quasi una pratica da sbrigare per compiacenza generale di clienti e colleghi, nel caso degli smart contract diventa una pratica alla quale non vorremo mai rinunciare.
Auditor dove siete?
Sempre più progetti pubblicano i risultati dei loro audit su codice solidity e la cosa è molto apprezzabile, ci consente di imparare molto dagli audit degli altri e di capire meglio lo stato dell’arte su cos’è considerato un codice “relativamente” sicuro, o per lo meno un codice audited.
Uno dei framework più affermati per iniziare, almeno iniziare, con codice sicuro (per quanto si possa essere sicuri di qualcosa) è sicuramente Open Zeppelin. Tra le librerie fornite da Open Zeppelin c’è Safe Math
SafeMath
Il problema con solidity è che non fornisce un controllo sull’overflow della somma fra due numeri, o in generale sul risultato di un’operazione aritmetica. Ad esempio nel caso della somma se questa supera il max integer value di (2^256-1). Safe Math aggiunge un controllo sul risultato. Allo stesso modo per molte altre operazioni aritmetiche.
function safeAdd(uint256 x, uint256 y) internal returns(uint256) { uint256 z = x + y; assert((z >= x) && (z >= y)); return z; }
Naturalmente usare SafeMath per ogni operazione consuma più gas, quindi se siamo sicuri di non “sforare” con le operazioni aritmetiche non bisognerebbe usarlo.
Reentrancy
Un altro tipico problema è la reentrancy. Quando una funzione spedisce ether ad un account esterno in generale questo potrebbe essere un altro contratto. Un contratto può essere programmato per eseguire codice nel momento in cui riceve ether e in modo malevolo potrebbe re-invocare la funzione che lo sta “beneficiando” con lo scopo di sifonarla. L’errore tipico è spedire una quota di ether ad un indirizzo e solo dopo mettere a zero il suo balance in una tabella, come nell’esempio sotto.
// THIS CONTRACT CONTAINS A BUG - DO NOT USE contract Fund { /// Mapping of ether shares of the contract. mapping(address => uint) shares; /// Withdraw your share. function withdraw() { if (msg.sender.send(shares[msg.sender])) shares[msg.sender] = 0; } }
msg.sender potrebbe a sua volta invocare ancora withdraw( … ) prima che l’istruzione shares[msg.sender] = 0 venga eseguita. Un esempio di questo exploit è riportato qui
Un errore simile a questo è stato alla base del DAOsaster. Una versione corretta del “prelievo” prima di tutto mette a zero il balance del prelevante e solo dopo gli spedisce i suoi ether.
contract Fund { /// Mapping of ether shares of the contract. mapping(address => uint) shares; /// Withdraw your share. function withdraw() { var share = shares[msg.sender]; shares[msg.sender] = 0; msg.sender.transfer(share); } }
Ultimo caso analizzato in questa parte del post è un caso in realtà non legato a Ethereum ma valido in generale per tutti i linguaggi con blocchi di codice separati da parentesi (python è immune), non fate if su più linee senza parentesi.
Multi line if without brackets
function destroyDeed() {
if (active) throw;
if(owner.send(this.balance))
selfdestruct(burn);
}
Questo stile di programmazione porta molto probabilmente un refactoring o un futuro sviluppo del codice ad aggiungere qualche istruzione in modo errato, come sotto.
function destroyDeed() {
if (active) throw;
if(owner.send(this.balance))
runPreDestroy();
selfdestruct(burn);
}
Rispondi