Packages

Aave V3 protocol wrappers for Elixir — pool reads/writes, oracle, math, and type structs. Built on onchain.

Current section

Files

Jump to
onchain_aave lib onchain aave math v4.ex
Raw

lib/onchain/aave/math/v4.ex

defmodule Onchain.Aave.Math.V4 do
@moduledoc """
Aave V4 integer-native math ports.
V4 split the V3 half-up WadRay helpers into explicit down/up variants. These
functions preserve the deployed Solidity formulas for valid non-overflowing
inputs while relying on BEAM arbitrary-precision integers instead of uint256
overflow reverts.
Source: `aave/aave-v4` `src/libraries/math/{WadRayMath,MathUtils}.sol` and
`src/spoke/libraries/LiquidationLogic.sol`.
"""
@wad 1_000_000_000_000_000_000
@ray 1_000_000_000_000_000_000_000_000_000
@percentage_factor 10_000
@seconds_per_year 31_536_000
@health_factor_liquidation_threshold 1_000_000_000_000_000_000
defguardp is_liquidation_input(
health_factor_for_max_bonus,
liquidation_bonus_factor,
health_factor,
max_liquidation_bonus
)
when is_integer(health_factor_for_max_bonus) and health_factor_for_max_bonus >= 0 and
health_factor_for_max_bonus < @health_factor_liquidation_threshold and
is_integer(liquidation_bonus_factor) and liquidation_bonus_factor >= 0 and
is_integer(health_factor) and health_factor >= 0 and
health_factor < @health_factor_liquidation_threshold and is_integer(max_liquidation_bonus) and
max_liquidation_bonus >= @percentage_factor
@doc "Multiply two wad-scaled integers, rounding down."
@spec wad_mul_down(non_neg_integer(), non_neg_integer()) :: non_neg_integer()
def wad_mul_down(a, b) when is_integer(a) and is_integer(b) and a >= 0 and b >= 0 do
div(a * b, @wad)
end
@doc "Multiply two wad-scaled integers, rounding up."
@spec wad_mul_up(non_neg_integer(), non_neg_integer()) :: non_neg_integer()
def wad_mul_up(a, b) when is_integer(a) and is_integer(b) and a >= 0 and b >= 0 do
div_up(a * b, @wad)
end
@doc "Divide two wad-scaled integers, rounding down."
@spec wad_div_down(non_neg_integer(), pos_integer()) :: non_neg_integer()
def wad_div_down(a, b) when is_integer(a) and is_integer(b) and a >= 0 and b > 0 do
div(a * @wad, b)
end
@doc "Divide two wad-scaled integers, rounding up."
@spec wad_div_up(non_neg_integer(), pos_integer()) :: non_neg_integer()
def wad_div_up(a, b) when is_integer(a) and is_integer(b) and a >= 0 and b > 0 do
div_up(a * @wad, b)
end
@doc "Multiply two ray-scaled integers, rounding down."
@spec ray_mul_down(non_neg_integer(), non_neg_integer()) :: non_neg_integer()
def ray_mul_down(a, b) when is_integer(a) and is_integer(b) and a >= 0 and b >= 0 do
div(a * b, @ray)
end
@doc "Multiply two ray-scaled integers, rounding up."
@spec ray_mul_up(non_neg_integer(), non_neg_integer()) :: non_neg_integer()
def ray_mul_up(a, b) when is_integer(a) and is_integer(b) and a >= 0 and b >= 0 do
div_up(a * b, @ray)
end
@doc "Divide two ray-scaled integers, rounding down."
@spec ray_div_down(non_neg_integer(), pos_integer()) :: non_neg_integer()
def ray_div_down(a, b) when is_integer(a) and is_integer(b) and a >= 0 and b > 0 do
div(a * @ray, b)
end
@doc "Divide two ray-scaled integers, rounding up."
@spec ray_div_up(non_neg_integer(), pos_integer()) :: non_neg_integer()
def ray_div_up(a, b) when is_integer(a) and is_integer(b) and a >= 0 and b > 0 do
div_up(a * @ray, b)
end
@doc "Cast an integer to wad scale by multiplying by 10^18."
@spec to_wad(non_neg_integer()) :: non_neg_integer()
def to_wad(a) when is_integer(a) and a >= 0 do
a * @wad
end
@doc "Cast an integer to ray scale by multiplying by 10^27."
@spec to_ray(non_neg_integer()) :: non_neg_integer()
def to_ray(a) when is_integer(a) and a >= 0 do
a * @ray
end
@doc "Remove wad precision, rounding down."
@spec from_wad_down(non_neg_integer()) :: non_neg_integer()
def from_wad_down(a) when is_integer(a) and a >= 0 do
div(a, @wad)
end
@doc "Remove ray precision, rounding up."
@spec from_ray_up(non_neg_integer()) :: non_neg_integer()
def from_ray_up(a) when is_integer(a) and a >= 0 do
div_up(a, @ray)
end
@doc "Convert basis points to wad scale."
@spec bps_to_wad(non_neg_integer()) :: non_neg_integer()
def bps_to_wad(a) when is_integer(a) and a >= 0 do
a * div(@wad, @percentage_factor)
end
@doc "Convert basis points to ray scale."
@spec bps_to_ray(non_neg_integer()) :: non_neg_integer()
def bps_to_ray(a) when is_integer(a) and a >= 0 do
a * div(@ray, @percentage_factor)
end
@doc "Round a ray value up to the nearest whole ray."
@spec round_ray_up(non_neg_integer()) :: non_neg_integer()
def round_ray_up(a) when is_integer(a) and a >= 0 do
div_up(a, @ray) * @ray
end
@doc "Calculate V4 linear interest between two timestamps at a ray-scaled rate."
@spec calculate_linear_interest(non_neg_integer(), non_neg_integer(), non_neg_integer()) ::
non_neg_integer()
def calculate_linear_interest(rate, last_update_timestamp, current_timestamp)
when is_integer(rate) and rate >= 0 and is_integer(last_update_timestamp) and last_update_timestamp >= 0 and
is_integer(current_timestamp) and current_timestamp >= last_update_timestamp do
@ray + div(rate * (current_timestamp - last_update_timestamp), @seconds_per_year)
end
@doc "Return the smaller of two non-negative integers."
@spec min(non_neg_integer(), non_neg_integer()) :: non_neg_integer()
def min(a, b) when is_integer(a) and is_integer(b) and a >= 0 and b >= 0 do
Kernel.min(a, b)
end
@doc "Subtract with a floor at zero."
@spec zero_floor_sub(non_neg_integer(), non_neg_integer()) :: non_neg_integer()
def zero_floor_sub(a, b) when is_integer(a) and is_integer(b) and a >= 0 and b >= 0 and a > b do
a - b
end
def zero_floor_sub(a, b) when is_integer(a) and is_integer(b) and a >= 0 and b >= 0 do
0
end
@doc "Add a signed integer to a non-negative integer, rejecting underflow."
@spec add(non_neg_integer(), integer()) :: non_neg_integer()
def add(a, b) when is_integer(a) and is_integer(b) and a >= 0 and b >= 0 do
a + b
end
def add(a, b) when is_integer(a) and is_integer(b) and a >= 0 and b < 0 and a >= -b do
a + b
end
@doc "Divide two integers, rounding up."
@spec div_up(non_neg_integer(), pos_integer()) :: non_neg_integer()
def div_up(a, b) when is_integer(a) and is_integer(b) and a >= 0 and b > 0 do
div(a, b) + if(rem(a, b) > 0, do: 1, else: 0)
end
@doc "Multiply two integers and divide by a third, rounding down."
@spec mul_div_down(non_neg_integer(), non_neg_integer(), pos_integer()) :: non_neg_integer()
def mul_div_down(a, b, c) when is_integer(a) and is_integer(b) and is_integer(c) and a >= 0 and b >= 0 and c > 0 do
div(a * b, c)
end
@doc "Multiply two integers and divide by a third, rounding up."
@spec mul_div_up(non_neg_integer(), non_neg_integer(), pos_integer()) :: non_neg_integer()
def mul_div_up(a, b, c) when is_integer(a) and is_integer(b) and is_integer(c) and a >= 0 and b >= 0 and c > 0 do
div_up(a * b, c)
end
@doc "Calculate V4's liquidation bonus from health-factor and bonus parameters."
@spec calculate_liquidation_bonus(
non_neg_integer(),
non_neg_integer(),
non_neg_integer(),
non_neg_integer()
) :: non_neg_integer()
def calculate_liquidation_bonus(
health_factor_for_max_bonus,
liquidation_bonus_factor,
health_factor,
max_liquidation_bonus
)
when is_liquidation_input(
health_factor_for_max_bonus,
liquidation_bonus_factor,
health_factor,
max_liquidation_bonus
) and health_factor <= health_factor_for_max_bonus do
max_liquidation_bonus
end
def calculate_liquidation_bonus(
health_factor_for_max_bonus,
liquidation_bonus_factor,
health_factor,
max_liquidation_bonus
)
when is_liquidation_input(
health_factor_for_max_bonus,
liquidation_bonus_factor,
health_factor,
max_liquidation_bonus
) do
min_liquidation_bonus =
percent_mul_down(max_liquidation_bonus - @percentage_factor, liquidation_bonus_factor) +
@percentage_factor
min_liquidation_bonus +
mul_div_down(
max_liquidation_bonus - min_liquidation_bonus,
@health_factor_liquidation_threshold - health_factor,
@health_factor_liquidation_threshold - health_factor_for_max_bonus
)
end
@spec percent_mul_down(non_neg_integer(), non_neg_integer()) :: non_neg_integer()
defp percent_mul_down(value, percentage) when is_integer(value) and is_integer(percentage) do
div(value * percentage, @percentage_factor)
end
end