WadRayLibrary
Fixed-point Math
There are only two numerical data types w.r.t the EVM:
signed integers, int256
unsigned integers, uint256
There is no support for fractional arithmetic. Hence, to support decimals places and such, is the use of fixed-point numbers. These are basically simple fractions whose denominator is a predefined constant, usually a power of 2 (“binary”) or 10 (“decimal”). The standard choices of denominator in the decimal case are 10¹⁸ (“wad”) or 10²⁷ (“ray”).
Wad and Ray
Presently, solidity compiler doesn’t support fixed-point mathematics yet. Hence, it is not possible to represent the number 3.1415
natively as a fixed-point number. The common workaround is by representing decimal numbers as unsigned integers and specifying the level of precision that is being used alongside it.
wad
is a decimal number with 18 digits of precision that is being represented as an integer.ray
is a decimal number with 27 digits of precision that is being represented as an integer.
Using wad and rays are pretty straightforward for addition and subtraction, but when performing multiplication or division one must rescale the result to get the right answer.
Example
The scaling required when multiplying and dividing wads is as follows:
Division should always be done last to preserve precision
Note that in solidity natively rounds down; therefore in our examples of mul and div, our results were rounded down. We will see how to WadRayLibrary employs mul and div functions that allow for rounding to the nearest integer.
WadRayLibrary: Mul & div w/ rounding
Rounding in solidity
All integer division rounds down to the nearest integer.
So we cannot natively rely on Solidity for rounding to the nearest integer, or similar use cases. Hence, the use of WadRayLibrary in Aave.
Provides mul and div function for wads (decimal numbers with 18 digits of precision) and rays (decimal numbers with 27 digits of precision)
Operations are rounded. If a value is >= 0.5, will be rounded up, otherwise rounded down
Rounding in WadRayMath
Rounding deals with fractional values.
Since solidity always rounds down, we can allow for rounding to the nearest integer by adding half
if remainder < 0.5 -> remainder +0.5 < 1 => rounded down to 0
if remainder 0.5 -> remainder + 0.5 1 => rounded up to 1
However, we cannot simply apply this as:
As the rounding would have already occurred as part of the division operation. We need to repackage this such that the division occurs last:
General Form
To allow for rounding to nearest integer, add half of the divisor to the dividend.
Let's connect this understanding with the implementation of wadDiv
in the library.
wadDiv
divides two wads, rounding half wad and above up to the nearest wad.
The division occurs at:
Multiply a WAD to negate the WAD removal from division. This ensures that WAD representation is adhered to.
Add half of the divisor (
b / 2
) to the dividend,a
to allow for rounding to the nearest integer .The addition of half b was explained earlier.
Overflow check
To avoid overflow,
a <= (type(uint256).max - HALF_WAD) / b
The check is executed first, so that if it fails, transaction can revert early saving on gas.
The
if or()
condition is required to account for the possibility that the divisor, b, is 0.
div by zero in Yul returns
0
->a/0 = 0
. This should revert, but does not.
This condition prevents the result of the multiplication from exceeding the maximum value that can be represented by a uint256
.
Both
a
andb
are passed into the function as uint256 parameters; so we do not check them.However, some manipulations are applied to the dividend, as part of our rounding effort and WAD representation:
dividend, A
:[(a * WAD ) + (b/2)]
, for A/bwe need to ensure that A does not overflow
when using assembly, we must apply {over,under}flow checks ourselves.
Since Solidity v0.8, the compiler has checks for {over,under}flow by default for all arithmetic operations. These checks do not apply to assembly (yul) arithmetic operations.
The bitwise negation operation, denoted as not
, flips all the bits in the binary representation of the given value.
not(0)
flips all the bits of0
, resulting in a value where all bits are set to1
not(0)
in YUL would be equivalent to2^256 - 1
not(0) => type(uint256).max
wadMul
The multiplication occurs at:
WAD / 2
: Add half of the divisor to the dividend, to allow for rounding to the nearest integer.Divide by WAD to negate the additional WAD from multiplication. This ensures that WAD representation is adhered to.
The above applies to rays just the same, as such we will not go through them in detail.
Converting between Wad and Ray
wad: 1e18, ray: 1e27
difference: 1e(27-9) = 1e9
wad to ray
Scaling a wad up to ray is easy: simply multiply by the difference in precision:
All the overflow check does is ensure that b/WAD_RAY_RATIO == a
, returns 1, which would be representative of true.
If
b/WAD_RAY_RATIO
!=
a
, the function will revert as there has been overflow
ray to wad
Scaling a ray down involves some loss of precision, as we move from 27 dp to 18 dp.
reduce the given ray by
1e9
to obtain a wad: 1e27 -> 1e18get the remainder of
ray/1e9
via modulo operationrounding to nearest wad:
if
remainder
1e9/2
, add 1 to resultb
Use of iszero
+ lt
to represent
Remember, when dividing and wanting to achieve rounding to the nearest integer (wad/ray), we need to add half of the divisor to the dividend, as explained earlier.
From Aave-v2-whitepaper:
The V1 WadRayMathlibrary internally uses SafeMath to guarantee the integrity of operations.
After an in depth analysis, SafeMath incurred intensive high costs in critical areas of the protocol, with a 30 gas fee for each call.
This supported the refactoring of WadRayMath to remove SafeMath, which saves 10-15k gas on some operations.
Last updated