Decimals Solidity OpenZeppelin/Web3

Ich hatte in letzter Zeit viel mit Nachkommastellen in Solidity mit OpenZeppelin zu tun und wollte meine Erkenntnisse in einem kurzen Blogpost zusammenfassen. Ich treffe hierbei einige Annahmen die auf intensivem Testen beruhen oder potentiell unseriösen Quellen -> gerne in den Kommentaren auf Fehler hinweisen!

Smart Contracts in Solidity die auf der EVM ausgeführt werden haben eine Präzision von 18. Ich meine damit z.B. 0.123456789012345678

Die Zahl an Dezimalstelle 18 wird auf der EVM laut diesem Stackoverflow Beitrag verworfen

Die Präzision von plain Javascript ist 15, daher ist für Tests mit den letzten Nachkommastellen die BigNumber library absolut notwendig! Hierbei auch beachten, dass statt BigNumber(0.123456789012345678) der Parameter als String, also BigNumber(„0.123456789012345678“) übergeben werden muss (sonst letzte 3 Zahlen nicht genau)

Berechnet man eine erwartete Zahl mit BigNumber und vergleicht sie dann, so kann aufgrund des Unterschiedes in der Präzision (BigNumber 20, EVM 18) ein unerwarteter Fehler beim Vergleich/Assert auftreten. Dieser ist schwer zu prüfen weil z.B. der Taschenrechner unter macOS nur 15 Nachkommastellen anzeigt. Hier kann der online Full Precision Calculator mit 200 Nachkommastellen sehr hilfreich sein.

Man muss also die Präzision in BigNumber von maximal 20 auf maximal 18 reduzieren und dabei die letzten 2 Nachkommastellen gegebenenfalls verwerfen. Aber was heißt verwerfen denn BigNumber kennt 9 verschiedene Rundungsarten, also welche nehmen? Die meisten Rundungsarten sind hier mit etwas mehr Text und einer Tabelle aufgeschlüsselt. ROUND_DOWN zeigt das gewünschte Verhalten ABER bitte noch weiter lesen!

Will man nun wirklich auf 18 abschneiden so kann man das so tun:

//note: 17 instead of 18 produces 18 decimals after 0.*
const reducedPrecision = number.toPrecision(17, BigNumber.ROUND_DOWN)

Will man mit Kommazahlen multiplizieren so benötigt man die Library ds-math

Probiert man in ds-math die Berechnung 1 / 74 = 0.0135135135135135135 wo ein 5er an der 19. Nachkommastelle kommt, so stellt man fest, dass an dieser Stelle aber anscheinend doch kaufmännisch gerundet wird. Das Ergebnis nach ds-math wdiv(1*1e18, 74) ist nämlich 0.013513513513513514

Also ist ROUND_HALF_UP, also kaufmännisches Runden wohl das Richtige…

Disclaimer: Könnte auch ein Bug in ganache-cli sein. Getestet in Version:
Ganache CLI v6.2.3 (ganache-core: 2.3.1)

​UPDATE:
openzeppelin hat die BigNumber Unterstützung aufgelöst und verwendet nun nurnoch web3.utils.BN, also BN.js. Hier sind wie in den Contracts keine Dezimal-Multiplikationen etc. möglich. Falls notwendig einfach von DSMath wmul() Funktion abschauen…


Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert