WadRayLibrary
Last updated
Last updated
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”).
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
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.
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 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
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.
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
and b
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/b
we need to ensure that A does not overflow
when using assembly, we must apply {over,under}flow checks ourselves.
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 of 0
, resulting in a value where all bits are set to 1
not(0)
in YUL would be equivalent to 2^256 - 1
not(0) => type(uint256).max
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.
wad: 1e18, ray: 1e27
difference: 1e(27-9) = 1e9
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
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 -> 1e18
get the remainder of ray/1e9
via modulo operation
rounding to nearest wad:
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.
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.
if remainder 0.5 -> remainder + 0.5 1 => rounded up to 1
, the compiler has checks for {over,under}flow by default for all arithmetic operations. These checks do not apply to assembly () arithmetic operations.
if remainder
1e9/2
, add 1 to result b
Use of iszero
+ lt
to represent
From :