-- WadrayVerification/Wadray.lean

import CorelibVerification

import WadrayVerification.Aux

import WadrayVerification.Load

open Sierra

-- Repository definitions used:
content: def RAY_SCALE : ℕ := 1000000000000000000000000000

content: protected def add : Ray := r.toZMod + r'.toZMod

content: protected def toZMod : UInt128 := r

content: def Ray : Type := UInt128

content: protected def toRat : ℚ := w.toZMod.val / WAD_SCALE

content: def WAD_SCALE : ℕ := 1000000000000000000

content: protected def toRat : ℚ := r.toZMod.val / RAY_SCALE

content: def Wad.toRay (w : Wad) : Ray := w.toZMod * (Ray.DIFF : UInt128)

content: def DIFF : ℕ := 1000000000

content: protected def toZMod : UInt128 := w

content: def toRat : ℚ := if SierraBool.toBool w.2 then -(Ray.toRat w.1) else Ray.toRat w.1

content: protected def sub : Ray := r.toZMod - r'.toZMod

content: protected def ofZMod (a : UInt128) : Ray := a

content: def Wad.MAX_CONVERTIBLE_WAD : ℕ := 340282366920938463463374607431

content: protected def ofZMod (a : UInt128) : Wad := a

content: def Wad : Type := UInt128

content: def toWad : Wad := r.toZMod.ndiv DIFF

content: def toRat : ℚ := if w.2 then -(Wad.toRat w.1) else Wad.toRat w.1

content: def SignedWad := UInt128 × (Unit ⊕ Unit)

content: def SignedRay := UInt128 × (Unit ⊕ Unit)

content: protected def mul : Ray := (r.toZMod.val * r'.toZMod.val / RAY_SCALE  : UInt128)

content: protected def mul : Wad := (w.toZMod.val * w'.toZMod.val / WAD_SCALE : UInt128)

content: protected def div : Ray := (r.toZMod.val * RAY_SCALE / r'.toZMod.val : UInt128)

content: protected def div : Wad := (w.toZMod.val * WAD_SCALE / w'.toZMod.val : UInt128)

-- Repository lemmas used:
content: theorem toRat_lt_toRat_of_val_lt_val (h : @ZMod.val U128_MOD r < @ZMod.val U128_MOD r') :
    r.toRat < r'.toRat

content: theorem RAY_SCALE_rat_pos : 0 < (RAY_SCALE : ℚ)

content: theorem toRat_lt_toRat_of_val_lt_val (h : @ZMod.val U128_MOD w < @ZMod.val U128_MOD w') :
    w.toRat < w'.toRat

content: theorem WAD_SCALE_rat_pos : 0 < (WAD_SCALE : ℚ)

content: theorem toRat_le_toRat_of_val_le_val (h : @ZMod.val U128_MOD r ≤ @ZMod.val U128_MOD r') :
    r.toRat ≤ r'.toRat

content: theorem toRat_le_toRat_of_val_le_val (h : @ZMod.val U128_MOD w ≤ @ZMod.val U128_MOD w') :
    w.toRat ≤ w'.toRat

content: theorem zero_def : (0 : SignedWad) = (0, false.toSierraBool)

content: theorem zero_def : (0 : SignedRay) = (0, false.toSierraBool)

content: protected theorem mul_def :
    r * r' = (r.toZMod.val * r'.toZMod.val / RAY_SCALE  : UInt128)

content: theorem mul_def :
    w₁ * w₂ = ⟨Ray.mul w₁.1 w₂.1, Bool.toSierraBool (Bool.xor (SierraBool.toBool w₁.2) (SierraBool.toBool w₂.2))⟩

content: protected theorem mul_def :
    w * w' = (w.toZMod.val * w'.toZMod.val / WAD_SCALE  : UInt128)

content: protected theorem div_def :
    r / r' = (r.toZMod.val * RAY_SCALE / r'.toZMod.val : UInt128)

content: theorem mul_def :
    w₁ * w₂ = ⟨Wad.mul (w₁.1 : Wad) (w₂.1 : Wad), Bool.toSierraBool (Bool.xor (SierraBool.toBool w₁.2) (SierraBool.toBool w₂.2))⟩

content: protected theorem div_def :
    w / w' = (w.toZMod.val * WAD_SCALE / w'.toZMod.val : UInt128)

content: theorem div_def :
    w₁ / w₂ = ⟨Ray.div (w₁.1 : Ray) (w₂.1 : Ray), Bool.toSierraBool (Bool.xor (SierraBool.toBool w₁.2) (SierraBool.toBool w₂.2))⟩

content: theorem RAY_SCALE_pos : 0 < RAY_SCALE

content: theorem div_def :
    w₁ / w₂ = ⟨Wad.div (w₁.1 : Wad) (w₂.1 : Wad), Bool.toSierraBool (Bool.xor (SierraBool.toBool w₁.2) (SierraBool.toBool w₂.2))⟩

-- Local definitions used:

-- Local lemmas used:
content: theorem Bool.toSierraBool_def (b : Bool) : b.toSierraBool = if b then .inr () else .inl ()

-- Target theorem and proof:
theorem RAY_SCALE_val :
    (1000000000000000000000000000 : UInt128).val = 1000000000000000000000000000 := rfl

aegis_spec "wadray::wadray::u128_rmul"
:= rfl

aegis_spec "wadray::wadray::u128_rmul" :=
  fun _ _ a b _ ρ =>
  (a.val * b.val / Ray.RAY_SCALE < U128_MOD ∧ ρ = .inl (a.val * b.val / Ray.RAY_SCALE))
  ∨ (U128_MOD ≤ a.val * b.val / Ray.RAY_SCALE ∧ ρ.isRight)

aegis_prove "wadray::wadray::u128_rmul" :=
  fun _ _ a b _ ρ => by
  unfold «spec_wadray::wadray::u128_rmul»
  sierra_simp'
  rintro ⟨_,(⟨h₁,rfl⟩|⟨h₁,h₁'⟩),h₂⟩
  · simp only [UInt256.val, ZMod.val_zero, mul_zero, zero_add] at h₁
    rcases h₂ with (⟨_,_,h₂,h₃,h₄⟩|h₂)
    · injection h₂ with h₂; subst h₂
      rcases h₃ with (⟨_,_,rfl,h₅⟩|⟨h₃,_⟩)
      · rcases h₄ with (⟨⟨wₗ,wₕ⟩,v,h₄,h₆,h₇⟩|h₄)
        · injection h₄ with h₄; subst h₄
          simp [UInt256.val, UInt256.mul_def, ← ZMod.val_mul_val_eq_hmul] at h₅  -- TODO
          rw [UInt256.val_lt_U128_MOD_iff, UInt256.U128_MOD_le_val_iff] at h₆
          rcases h₆ with (⟨rfl,rfl⟩|⟨h₆,rfl⟩)
          · rw [ZMod.val_zero, mul_zero, zero_add] at h₅
            simp only [Sum.inl.injEq, exists_eq_left, Nat.cast_ofNat, Int.cast_ofNat,
              List.nil_append, false_and, exists_const, or_false] at h₇; cases h₇
            erw [← h₅]
            simp only [wₗ.val_lt, Sum.inl.injEq, true_and, Sum.isRight_inl, and_false, or_false]
            rw [@ZMod.natCast_zmod_val]
          · simp only [false_and, exists_const, Nat.cast_ofNat, Int.cast_ofNat, List.nil_append,
              true_and, false_or] at h₇; cases h₇
            rw [← UInt256.U128_MOD_le_val_iff] at h₆
            erw [← h₅]
            refine .inr ⟨h₆, ?_⟩
            simp
        · simp only [false_and, exists_false] at h₄
      · injection h₃ with h₃
        rw [ZMod.intCast_zmod_eq_zero_iff_dvd] at h₃
        norm_num[U128_MOD] at h₃
    · simp at h₂
  · simp only [UInt256.val, ZMod.val_zero, mul_zero, zero_add] at h₁
    right
    constructor
    · rw [Nat.le_div_iff_mul_le' Ray.RAY_SCALE_pos]
      apply le_trans _ h₁
      norm_num [U128_MOD, Ray.RAY_SCALE, U256_MOD]
    · aesop

aegis_spec "wadray::wadray::rmul" :=
  fun _ _ (a b : Ray) _ (ρ : Ray ⊕ _) =>
  (a.toZMod.val * b.toZMod.val / Ray.RAY_SCALE < U128_MOD ∧ ρ = .inl (a * b))
  ∨ (U128_MOD ≤ a.toZMod.val * b.toZMod.val / Ray.RAY_SCALE ∧ ρ.isRight)

aegis_prove "wadray::wadray::rmul" :=
  fun _ _ (a b : Ray) _ (ρ : Ray ⊕ _) => by
  unfold «spec_wadray::wadray::rmul»
  sierra_simp'
  aesop

aegis_spec "wadray::wadray::WadSub::sub" :=
  fun _ _ (a b : Wad) _ (ρ : Wad ⊕ _) =>
  (b.toRat ≤ a.toRat ∧ ρ = .inl (a - b))
  ∨ (a.toRat < b.toRat ∧ ρ.isRight)

aegis_prove "wadray::wadray::WadSub::sub" :=
  fun _ _ (a b : Wad) _ (ρ : Wad ⊕ _) => by
  unfold «spec_wadray::wadray::WadSub::sub»
  aesop (add safe forward [Wad.toRat_le_toRat_of_val_le_val, Wad.toRat_lt_toRat_of_val_lt_val])

aegis_spec "wadray::wadray::WadAdd::add" :=
  fun _ _ (a b : Wad) _ (ρ : Wad ⊕ _) =>
  (a.toZMod.val + b.toZMod.val < U128_MOD ∧ ρ = .inl (a + b))
  ∨ (U128_MOD ≤ a.toZMod.val + b.toZMod.val ∧ ρ.isRight)

aegis_prove "wadray::wadray::WadAdd::add" :=
  fun _ _ (a b : Wad) _ (ρ : Wad ⊕ _) => by
  unfold «spec_wadray::wadray::WadAdd::add»
  aesop

aegis_spec "wadray::wadray::RaySub::sub" :=
  fun _ _ (a b : Ray) _ (ρ : Ray ⊕ _) =>
  (b.toRat ≤ a.toRat ∧ ρ = .inl (a - b))
  ∨ (a.toRat < b.toRat ∧ ρ.isRight)

aegis_prove "wadray::wadray::RaySub::sub" :=
  fun _ _ (a b : Ray) _ (ρ : Ray ⊕ _) => by
  unfold «spec_wadray::wadray::RaySub::sub»
  aesop (add safe forward [Ray.toRat_le_toRat_of_val_le_val, Ray.toRat_lt_toRat_of_val_lt_val])

aegis_spec "wadray::wadray::RayAdd::add" :=
  fun _ _ (a b : Ray) _ (ρ : Ray ⊕ _) =>
  (a.toZMod.val + b.toZMod.val < U128_MOD ∧ ρ = .inl (a + b))
  ∨ (U128_MOD ≤ a.toZMod.val + b.toZMod.val ∧ ρ.isRight)

aegis_prove "wadray::wadray::RayAdd::add" :=
  fun _ _ (a b : Ray) _ (ρ : Ray ⊕ _) => by
  unfold «spec_wadray::wadray::RayAdd::add»
  aesop

aegis_spec "wadray::wadray::WadZeroable::is_non_zero" :=
  fun _ (a : Wad) ρ =>
  ρ = Bool.toSierraBool (a.toRat ≠ 0)

aegis_prove "wadray::wadray::WadZeroable::is_non_zero" :=
  fun _ (a : Wad) ρ => by
  unfold «spec_wadray::wadray::WadZeroable::is_non_zero»
  aesop (add simp norm [Wad.toRat, Wad.toZMod, ZMod.cast_rat_eq_zero_iff])

aegis_spec "wadray::wadray::WadZeroable::is_zero" :=
  fun _ (a : Wad) ρ =>
  ρ = Bool.toSierraBool (a.toRat = 0)

aegis_prove "wadray::wadray::WadZeroable::is_zero" :=
  fun _ (a : Wad) ρ => by
  unfold «spec_wadray::wadray::WadZeroable::is_zero»
  aesop (add simp norm [Bool.toSierraBool_def, Wad.toRat, ZMod.cast_rat_eq_zero_iff, Wad.toZMod])

aegis_spec "wadray::wadray::RayZeroable::is_zero" :=
  fun _ (a : Ray) ρ =>
  ρ = Bool.toSierraBool (a.toRat = 0)

aegis_prove "wadray::wadray::RayZeroable::is_zero" :=
  fun _ (a : Ray) ρ => by
  unfold «spec_wadray::wadray::RayZeroable::is_zero»
  aesop (add simp norm [Bool.toSierraBool_def, Ray.toRat, ZMod.cast_rat_eq_zero_iff, Ray.toZMod])

aegis_spec "wadray::wadray::RayZeroable::is_non_zero" :=
  fun _ (a : Ray) ρ =>
  ρ = Bool.toSierraBool (a.toRat ≠ 0)

aegis_prove "wadray::wadray::RayZeroable::is_non_zero" :=
  fun _ (a : Ray) ρ => by
  unfold «spec_wadray::wadray::RayZeroable::is_non_zero»
  aesop (add simp norm [Ray.toRat, Ray.toZMod, ZMod.cast_rat_eq_zero_iff])

aegis_spec "wadray::wadray::BoundedWad::min" :=
  fun _ ρ =>
  ρ = 0

aegis_prove "wadray::wadray::BoundedWad::min" :=
  fun _ ρ => by
  rintro rfl
  rfl

aegis_spec "wadray::wadray::BoundedRay::min" :=
  fun _ ρ =>
  ρ = 0

aegis_prove "wadray::wadray::BoundedRay::min" :=
  fun _ ρ => by
  rintro rfl
  rfl

aegis_spec "wadray::wadray::BoundedWad::max" :=
  fun _ ρ =>
  ρ = U128_MOD - 1

aegis_prove "wadray::wadray::BoundedWad::max" :=
  fun _ ρ => by
  rintro rfl
  rfl

aegis_spec "wadray::wadray::BoundedRay::max" :=
  fun _ ρ =>
  ρ = U128_MOD - 1

aegis_prove "wadray::wadray::BoundedRay::max" :=
  fun _ ρ => by
  rintro rfl
  rfl

aegis_spec "wadray::wadray::WadAddEq::add_eq" :=
  fun _ _ (a b : Wad) _ (ρ : Wad × _ ⊕ _) =>
  (a.toZMod.val + b.toZMod.val < U128_MOD ∧ ρ = .inl (a + b, ()))
  ∨ (U128_MOD ≤ a.toZMod.val + b.toZMod.val ∧ ρ.isRight)

aegis_prove "wadray::wadray::WadAddEq::add_eq" :=
  fun _ _ (a b : Wad) _ (ρ : Wad × _ ⊕ _) => by
  unfold «spec_wadray::wadray::WadAddEq::add_eq»
  aesop

aegis_spec "wadray::wadray::WadSubEq::sub_eq" :=
  fun _ _ (a b : Wad) _ (ρ : Wad × _ ⊕ _) =>
  (b.toRat ≤ a.toRat ∧ ρ = .inl (a - b, ()))
  ∨ (a.toRat < b.toRat ∧ ρ.isRight)

aegis_prove "wadray::wadray::WadSubEq::sub_eq" :=
  fun _ _ (a b : Wad) _ (ρ : Wad × _ ⊕ _) => by
  unfold «spec_wadray::wadray::WadSubEq::sub_eq»
  aesop

aegis_spec "wadray::wadray::WadMulEq::mul_eq" :=
  fun _ _ (a b : Wad) _ (ρ : Wad × _ ⊕ _) =>
  (a.toZMod.val * b.toZMod.val / Wad.WAD_SCALE < U128_MOD ∧ ρ = .inl (a * b, ()))
  ∨ (U128_MOD ≤ a.toZMod.val * b.toZMod.val / Wad.WAD_SCALE ∧ ρ.isRight)

aegis_prove "wadray::wadray::WadMulEq::mul_eq" :=
  fun _ _ (a b : Wad) _ (ρ : Wad × _ ⊕ _) => by
  unfold «spec_wadray::wadray::WadMulEq::mul_eq»
  aesop

aegis_spec "wadray::wadray::TIntoWad<core::integer::u128, core::traits::TIntoT<core::integer::u128>>::into" :=
  fun _ a (ρ : Wad) =>
  ρ = a

aegis_prove "wadray::wadray::TIntoWad<core::integer::u128, core::traits::TIntoT<core::integer::u128>>::into" :=
  fun _ a (ρ : Wad) => by
  unfold «spec_wadray::wadray::TIntoWad<core::integer::u128, core::traits::TIntoT<core::integer::u128>>::into»
  aesop

aegis_spec "wadray::wadray::TIntoRay<core::integer::u128, core::traits::TIntoT<core::integer::u128>>::into" :=
  fun _ a (ρ : Ray) =>
  ρ = a

aegis_prove "wadray::wadray::TIntoRay<core::integer::u128, core::traits::TIntoT<core::integer::u128>>::into" :=
  fun _ a (ρ : Ray) => by
  unfold «spec_wadray::wadray::TIntoRay<core::integer::u128, core::traits::TIntoT<core::integer::u128>>::into»
  aesop

aegis_spec "wadray::wadray::u128_wdiv" :=
  fun _ _ (a b : Wad) _ (ρ : Wad ⊕ _) =>
  a.toZMod.val * Wad.WAD_SCALE / b.toZMod.val < U128_MOD
    ∧ b.toZMod.val ≠ 0 ∧ ρ = .inl (a / b)
  ∨ (U128_MOD ≤ a.toZMod.val * Wad.WAD_SCALE / b.toZMod.val ∨ b.toZMod.val = 0) ∧ ρ.isRight

aegis_prove "wadray::wadray::u128_wdiv" :=
  fun _ _ (a b : Wad) _ (ρ : Wad ⊕ _) => by
  sierra_simp'
  unfold «spec_wadray::wadray::u128_wdiv»
  rintro ⟨(x|x),h₁,h₂⟩
  · simp_all only [Nat.cast_ofNat, Int.cast_ofNat, Sum.inl.injEq, Sum.isRight_inl,
      Bool.false_eq_true, and_false, or_false, ne_eq, exists_and_left, List.nil_append,
      exists_and_right, Sum.exists, exists_eq_left, exists_const, false_and, and_true, false_or,
      true_and, Prod.exists, exists_eq_left', Prod.mk.injEq, Sum.isRight_inr, Sum.inr.injEq,
      UInt256.val_mul_of_low, ZMod.val_eq_zero]
    rcases h₁ with ⟨h₁,rfl⟩
    simp only [UInt256.val, ZMod.val_zero, mul_zero, zero_add] at h₁
    rcases h₂ with (⟨_,_,⟨h₂,h₄⟩,_,_,⟨rfl,rfl⟩,h₃⟩|⟨h₂,_,_,rfl⟩)
    · simp [h₄, UInt256.val] at h₃
      simp only [UInt256.val, ZMod.val_zero, mul_zero, zero_add,
        UInt256.mul_def, mul_zero, add_zero, zero_mul] at h₄
      rcases ρ with (ρ|ρ)
      · simp only [Sum.inl.injEq, Sum.isRight_inl, and_false, or_false] at h₃ ⊢
        rcases h₃ with ⟨_,⟨h₃,rfl⟩,rfl⟩
        refine ⟨?_,?_,?_⟩
        · rwa [h₄] at h₃
        · simp only [UInt256.zero_def] at h₂
          simp only [Wad.toZMod]
          apply Aesop.BuiltinRules.not_intro
          intro h
          subst h
          simp_all only [not_true]
        · simp only [Wad.div_def, Wad.WAD_SCALE]
          erw [← h₄]; clear h₄
          simp [Nat.eq_zero_of_mul_lt_right (Nat.lt_of_add_lt h₃)]
      · simp only [and_false, exists_false, Sum.inr.injEq, false_or] at h₃
        simp only [and_false, Sum.isRight_inr, and_true, false_or]
        left
        rcases h₃ with ⟨h₃,rfl⟩
        rwa [h₄] at h₃
    · simp only [and_false, Sum.isRight_inr, and_true, false_or]
      right
      injection h₂
  · simp_all only [Nat.cast_ofNat, Int.cast_ofNat, and_false, Sum.isRight_inr, and_true, false_or,
      ne_eq, exists_and_left, List.nil_append, exists_and_right, Sum.exists, Sum.inl.injEq, or_false,
      exists_eq_left, exists_const, false_and, true_and, Prod.exists, Sum.inr.injEq, ZMod.val_eq_zero]
    subst h₂
    simp only [and_false, Sum.isRight_inr, and_true, false_or]
    by_cases h₃ : b.toZMod.val = 0
    · right
      rwa [ZMod.val_eq_zero] at h₃
    · left
      conv => lhs; rw [← U256_MOD_div]
      trans; apply Nat.div_le_div_left (le_of_lt b.toZMod.val_lt) (Nat.pos_of_ne_zero h₃)
      simp only [UInt256.val, ZMod.val_zero, mul_zero, zero_add] at h₁
      apply Nat.div_le_div_right h₁

aegis_spec "wadray::wadray::u128_rdiv" :=
  fun _ _ (a b : Ray) _ (ρ : Ray ⊕ _) =>
  (a.toZMod.val * Ray.RAY_SCALE / b.toZMod.val < U128_MOD ∧ b.toZMod.val ≠ 0 ∧ ρ = .inl (a / b))
  ∨ ((U128_MOD ≤ a.toZMod.val * Ray.RAY_SCALE / b.toZMod.val ∨ b.toZMod.val = 0) ∧ ρ.isRight)

aegis_prove "wadray::wadray::u128_rdiv" :=
  fun _ _ (a b : Ray) _ (ρ : Ray ⊕ _) => by
  sierra_simp'
  unfold «spec_wadray::wadray::u128_rdiv»
  rintro ⟨(x|x),h₁,h₂⟩
  · simp_all only [Nat.cast_ofNat, Int.cast_ofNat, Sum.inl.injEq, Sum.isRight_inl,
      Bool.false_eq_true, and_false, or_false, ne_eq, exists_and_left, List.nil_append,
      exists_and_right, Sum.exists, exists_eq_left, exists_const, false_and, and_true, false_or,
      true_and, Prod.exists, exists_eq_left', Prod.mk.injEq, Sum.isRight_inr, Sum.inr.injEq,
      UInt256.val_mul_of_low, ZMod.val_eq_zero]
    rcases h₁ with ⟨h₁,rfl⟩
    simp only [UInt256.val, ZMod.val_zero, mul_zero, zero_add] at h₁
    rcases h₂ with (⟨_,_,⟨h₂,h₄⟩,_,_,⟨rfl,rfl⟩,h₃⟩|⟨h₂,_,_,rfl⟩)
    · simp [h₄, UInt256.val] at h₃
      simp only [UInt256.val, ZMod.val_zero, mul_zero, zero_add,
        UInt256.mul_def, mul_zero, add_zero, zero_mul] at h₄
      rcases ρ with (ρ|ρ)
      · simp only [Sum.inl.injEq, Sum.isRight_inl, and_false, or_false] at h₃ ⊢
        rcases h₃ with ⟨_,⟨h₃,rfl⟩,rfl⟩
        refine ⟨?_,?_,?_⟩
        · rwa [h₄] at h₃
        · simp only [UInt256.zero_def] at h₂
          simp only [Ray.toZMod]
          apply Aesop.BuiltinRules.not_intro
          intro h
          subst h
          simp_all only [not_true]
        · simp only [Ray.div_def, Ray.RAY_SCALE]
          erw [← h₄]; clear h₄
          simp [Nat.eq_zero_of_mul_lt_right (Nat.lt_of_add_lt h₃)]
      · simp only [and_false, exists_false, Sum.inr.injEq, false_or] at h₃
        simp only [and_false, Sum.isRight_inr, and_true, false_or]
        left
        rcases h₃ with ⟨h₃,rfl⟩
        rwa [h₄] at h₃
    · simp only [and_false, Sum.isRight_inr, and_true, false_or]
      right
      injection h₂
  · simp_all only [Nat.cast_ofNat, Int.cast_ofNat, and_false, Sum.isRight_inr, and_true, false_or,
      ne_eq, exists_and_left, List.nil_append, exists_and_right, Sum.exists, Sum.inl.injEq, or_false,
      exists_eq_left, exists_const, false_and, true_and, Prod.exists, Sum.inr.injEq, ZMod.val_eq_zero]
    subst h₂
    simp only [and_false, Sum.isRight_inr, and_true, false_or]
    by_cases h₃ : b.toZMod.val = 0
    · right
      rwa [ZMod.val_eq_zero] at h₃
    · left
      conv => lhs; rw [← U256_MOD_div]
      trans; apply Nat.div_le_div_left (le_of_lt b.toZMod.val_lt) (Nat.pos_of_ne_zero h₃)
      simp only [UInt256.val, ZMod.val_zero, mul_zero, zero_add] at h₁
      apply Nat.div_le_div_right h₁

aegis_spec "wadray::wadray::wdiv" :=
  fun _ _ (a b : Wad) _ (ρ : Wad ⊕ _) =>
  (a.toZMod.val * Wad.WAD_SCALE / b.toZMod.val < U128_MOD ∧ b.toZMod.val ≠ 0 ∧ ρ = .inl (a / b))
  ∨ ((U128_MOD ≤ a.toZMod.val * Wad.WAD_SCALE / b.toZMod.val ∨ b.toZMod.val = 0) ∧ ρ.isRight)

aegis_prove "wadray::wadray::wdiv" :=
  fun _ _ (a b : Wad) _ (ρ : Wad ⊕ _) => by
  unfold «spec_wadray::wadray::wdiv»
  aesop

aegis_spec "wadray::wadray::WadDivEq::div_eq" :=
  fun _ _ (a b : Wad) _ (ρ : (Wad × _) ⊕ _) =>
  (a.toZMod.val * Wad.WAD_SCALE / b.toZMod.val < U128_MOD ∧ b.toZMod.val ≠ 0 ∧ ρ = .inl (a / b, ()))
  ∨ ((U128_MOD ≤ a.toZMod.val * Wad.WAD_SCALE / b.toZMod.val ∨ b.toZMod.val = 0) ∧ ρ.isRight)

aegis_prove "wadray::wadray::WadDivEq::div_eq" :=
  fun _ _ (a b : Wad) _ (ρ : (Wad × _) ⊕ _) => by
  unfold «spec_wadray::wadray::WadDivEq::div_eq»
  aesop

aegis_spec "wadray::wadray::rdiv" :=
  fun _ _ (a b : Ray) _ (ρ : Ray ⊕ _) =>
  (a.toZMod.val * Ray.RAY_SCALE / b.toZMod.val < U128_MOD ∧ b.toZMod.val ≠ 0 ∧ ρ = .inl (a / b))
  ∨ ((U128_MOD ≤ a.toZMod.val * Ray.RAY_SCALE / b.toZMod.val ∨ b.toZMod.val = 0) ∧ ρ.isRight)

aegis_prove "wadray::wadray::rdiv" :=
  fun _ _ (a b : Ray) _ (ρ : Ray ⊕ _) => by
  unfold «spec_wadray::wadray::rdiv»
  aesop

aegis_spec "wadray::wadray::RayIntoWad::into" :=
  fun _ _ (a : Ray) _ ρ =>
  ρ = .inl a.toWad

aegis_prove "wadray::wadray::RayIntoWad::into" :=
  fun _ _ (a : Ray) _ ρ => by
  unfold «spec_wadray::wadray::RayIntoWad::into»
  rintro ⟨_,_,_,(h|h),h'⟩
  · rcases h' with (⟨rfl,rfl⟩|⟨rfl,rfl⟩)
    · rcases h with ⟨_,ρ',h₁,h₂⟩
      cases h₁
      congr
      simp only [Ray.toWad, ZMod.ndiv, U128_MOD]
      apply ZMod.val_injective
      exact h₂
    · simp at h
  · rcases h with ⟨h,-⟩
    rw [ZMod.intCast_zmod_eq_zero_iff_dvd] at h
    norm_num [U128_MOD] at h

aegis_spec "wadray::wadray::WadIntoU256::into" :=
  fun _ a ρ =>
  ρ = (a, 0)

aegis_prove "wadray::wadray::WadIntoU256::into" :=
  fun _ a ρ => by
  unfold «spec_wadray::wadray::WadIntoU256::into»
  rintro rfl
  rfl

aegis_spec "wadray::wadray::U256TryIntoWad::try_into" :=
  fun _ (a : UInt256) ρ =>
  (a.val < U128_MOD ∧ ρ = .inl a.1)
  ∨ (U128_MOD ≤ a.val ∧ ρ = .inr ())

aegis_prove "wadray::wadray::U256TryIntoWad::try_into" :=
  fun _ (a : UInt256) ρ => by
  unfold «spec_wadray::wadray::U256TryIntoWad::try_into»
  aesop

aegis_spec "wadray::wadray::TIntoRay<core::integer::u64, core::integer::UpcastableInto<core::integer::u64, core::integer::u128, core::integer::UpcastableU64U128>>::into" :=
  fun _ a ρ =>
  ρ = a.cast

aegis_prove "wadray::wadray::TIntoRay<core::integer::u64, core::integer::UpcastableInto<core::integer::u64, core::integer::u128, core::integer::UpcastableU64U128>>::into" :=
  fun _ a ρ => by
  rintro rfl
  rfl

aegis_spec "wadray::wadray::TIntoRay<core::integer::u32, core::integer::UpcastableInto<core::integer::u32, core::integer::u128, core::integer::UpcastableU32U128>>::into" :=
  fun _ a ρ =>
  ρ = a.cast

aegis_prove "wadray::wadray::TIntoRay<core::integer::u32, core::integer::UpcastableInto<core::integer::u32, core::integer::u128, core::integer::UpcastableU32U128>>::into" :=
  fun _ a ρ => by
  rintro rfl
  rfl

aegis_spec "wadray::wadray::TIntoRay<core::integer::u16, core::integer::UpcastableInto<core::integer::u16, core::integer::u128, core::integer::UpcastableU16U128>>::into" :=
  fun _ a ρ =>
  ρ = a.cast

aegis_prove "wadray::wadray::TIntoRay<core::integer::u16, core::integer::UpcastableInto<core::integer::u16, core::integer::u128, core::integer::UpcastableU16U128>>::into" :=
  fun _ a ρ => by
  rintro rfl
  rfl

aegis_spec "wadray::wadray::TIntoRay<core::integer::u8, core::integer::UpcastableInto<core::integer::u8, core::integer::u128, core::integer::UpcastableU8U128>>::into" :=
  fun _ a ρ =>
  ρ = a.cast

aegis_prove "wadray::wadray::TIntoRay<core::integer::u8, core::integer::UpcastableInto<core::integer::u8, core::integer::u128, core::integer::UpcastableU8U128>>::into" :=
  fun _ a ρ => by
  rintro rfl
  rfl

aegis_spec "wadray::wadray::TIntoWad<core::integer::u64, core::integer::UpcastableInto<core::integer::u64, core::integer::u128, core::integer::UpcastableU64U128>>::into" :=
  fun _ a ρ =>
  ρ = a.cast

aegis_prove "wadray::wadray::TIntoWad<core::integer::u64, core::integer::UpcastableInto<core::integer::u64, core::integer::u128, core::integer::UpcastableU64U128>>::into" :=
  fun _ a ρ => by
  rintro rfl
  rfl

aegis_spec "wadray::wadray::TIntoWad<core::integer::u32, core::integer::UpcastableInto<core::integer::u32, core::integer::u128, core::integer::UpcastableU32U128>>::into" :=
  fun _ a ρ =>
  ρ = a.cast

aegis_prove "wadray::wadray::TIntoWad<core::integer::u32, core::integer::UpcastableInto<core::integer::u32, core::integer::u128, core::integer::UpcastableU32U128>>::into" :=
  fun _ a ρ => by
  rintro rfl
  rfl

aegis_spec "wadray::wadray::TIntoWad<core::integer::u16, core::integer::UpcastableInto<core::integer::u16, core::integer::u128, core::integer::UpcastableU16U128>>::into" :=
  fun _ a ρ =>
  ρ = a.cast

aegis_prove "wadray::wadray::TIntoWad<core::integer::u16, core::integer::UpcastableInto<core::integer::u16, core::integer::u128, core::integer::UpcastableU16U128>>::into" :=
  fun _ a ρ => by
  rintro rfl
  rfl

aegis_spec "wadray::wadray::TIntoWad<core::integer::u8, core::integer::UpcastableInto<core::integer::u8, core::integer::u128, core::integer::UpcastableU8U128>>::into" :=
  fun _ a ρ =>
  ρ = a.cast

aegis_prove "wadray::wadray::TIntoWad<core::integer::u8, core::integer::UpcastableInto<core::integer::u8, core::integer::u128, core::integer::UpcastableU8U128>>::into" :=
  fun _ a ρ => by
  rintro rfl
  rfl

aegis_spec "wadray::wadray::WadTryIntoRay::try_into" :=
  fun _ _ (a : Wad) _ (ρ : (Ray ⊕ _) ⊕ _) =>
  ρ = if (a.toZMod.val ≤ Wad.MAX_CONVERTIBLE_WAD) then .inl (.inl a.toRay)
      else .inl (.inr ())

aegis_prove "wadray::wadray::WadTryIntoRay::try_into" :=
  fun _ _ (a : Wad) _ (ρ : (Ray ⊕ _) ⊕ _) => by
  unfold «spec_wadray::wadray::WadTryIntoRay::try_into»
  have h₁ : ZMod.val (340282366920938463463374607431 : UInt128) = Wad.MAX_CONVERTIBLE_WAD := rfl
  have h₂ : ZMod.val (1000000000 : UInt128) = 1000000000 := rfl
  rintro ⟨_,_,_,(h|h)⟩
  · rcases h with ⟨h₃,(h₄|h₄),h₅⟩
    · aesop
    · rcases h₅ with (h₅|⟨rfl,rfl⟩)
      · aesop
      · simp only [Int.ofNat_eq_coe, Nat.cast_ofNat, Int.cast_ofNat, Sum.isRight_inr, and_true] at h₃ h₄
        rw [h₂] at h₄
        rw [h₁] at h₃
        replace h₄ := h₄.trans (Nat.mul_le_mul_right 1000000000 h₃)
        norm_num [U128_MOD, Wad.MAX_CONVERTIBLE_WAD] at h₄
  · aesop

aegis_spec "wadray::wadray::scale_u128_by_ray" :=
  fun _ _ a (b : Ray) _ ρ =>
  (a.val * b.toZMod.val / Ray.RAY_SCALE < U128_MOD ∧ ρ = .inl (a.val * b.toZMod.val / Ray.RAY_SCALE))
  ∨ (U128_MOD ≤ a.val * b.toZMod.val / Ray.RAY_SCALE ∧ ρ.isRight)

aegis_prove "wadray::wadray::scale_u128_by_ray" :=
  fun _ _ a (b : Ray) _ ρ => by
  unfold «spec_wadray::wadray::scale_u128_by_ray»
  aesop

aegis_spec "wadray::wadray::div_u128_by_ray" :=
  fun _ _ a (b : Ray) _ ρ =>
  a.val * Ray.RAY_SCALE / b.toZMod.val < U128_MOD ∧ b.toZMod.val ≠ 0
    ∧ ρ = .inl (a.val * Ray.RAY_SCALE / b.toZMod.val)
  ∨ (U128_MOD ≤ a.val * Ray.RAY_SCALE / b.toZMod.val ∨ b.toZMod.val = 0) ∧ ρ.isRight

aegis_prove "wadray::wadray::div_u128_by_ray" :=
  fun _ _ a (b : Ray) _ ρ => by
  unfold «spec_wadray::wadray::div_u128_by_ray»
  aesop

aegis_spec "wadray::wadray::wmul_wr" :=
  fun _ _ (a : Wad) (b : Ray) _ (ρ : Ray ⊕ _) =>
  a.toZMod.val * b.toZMod.val / Wad.WAD_SCALE < U128_MOD
    ∧ ρ = .inl (Ray.ofZMod (a.toZMod.val * b.toZMod.val / Wad.WAD_SCALE))
  ∨ U128_MOD ≤ a.toZMod.val * b.toZMod.val / Wad.WAD_SCALE ∧ ρ.isRight

aegis_prove "wadray::wadray::wmul_wr" :=
  fun _ _ (a : Wad) (b : Ray) _ (ρ : Ray ⊕ _) => by
  unfold «spec_wadray::wadray::wmul_wr»
  aesop

aegis_spec "wadray::wadray::wmul_rw" :=
  fun _ _ (a : Ray) (b : Wad) _ (ρ : Ray ⊕ _) =>
  a.toZMod.val * b.toZMod.val / Wad.WAD_SCALE < U128_MOD
    ∧ ρ = .inl (Ray.ofZMod (a.toZMod.val * b.toZMod.val / Wad.WAD_SCALE))
  ∨ U128_MOD ≤ a.toZMod.val * b.toZMod.val / Wad.WAD_SCALE ∧ ρ.isRight

aegis_prove "wadray::wadray::wmul_rw" :=
  fun _ _ (a : Ray) (b : Wad) _ (ρ : Ray ⊕ _) => by
  unfold «spec_wadray::wadray::wmul_rw»
  aesop (add simp [mul_comm])

aegis_spec "wadray::wadray::rmul_rw" :=
  fun _ _ (a : Ray) (b : Wad) _ (ρ : Wad ⊕ _) =>
  a.toZMod.val * b.toZMod.val / Ray.RAY_SCALE < U128_MOD
    ∧ ρ = .inl (Ray.ofZMod (a.toZMod.val * b.toZMod.val / Ray.RAY_SCALE))
  ∨ U128_MOD ≤ a.toZMod.val * b.toZMod.val / Ray.RAY_SCALE ∧ ρ.isRight

aegis_prove "wadray::wadray::rmul_rw" :=
  fun _ _ (a : Ray) (b : Wad) _ (ρ : Ray ⊕ _) => by
  unfold «spec_wadray::wadray::rmul_rw»
  aesop (add simp [mul_comm])

aegis_spec "wadray::wadray::rmul_wr" :=
  fun _ _ (a : Wad) (b : Ray) _ (ρ : Wad ⊕ _) =>
  a.toZMod.val * b.toZMod.val / Ray.RAY_SCALE < U128_MOD
    ∧ ρ = .inl (Ray.ofZMod (a.toZMod.val * b.toZMod.val / Ray.RAY_SCALE))
  ∨ U128_MOD ≤ a.toZMod.val * b.toZMod.val / Ray.RAY_SCALE ∧ ρ.isRight

aegis_prove "wadray::wadray::rmul_wr" :=
  fun _ _ (a : Ray) (b : Wad) _ (ρ : Ray ⊕ _) => by
  unfold «spec_wadray::wadray::rmul_wr»
  aesop (add simp [mul_comm])

aegis_spec "wadray::wadray::rdiv_wr" :=
  fun _ _ (a : Wad) (b : Ray) _ (ρ : Wad ⊕ _) =>
  a.toZMod.val * Ray.RAY_SCALE / b.toZMod.val < U128_MOD
    ∧ b.toZMod.val ≠ 0 ∧ ρ = .inl (Wad.ofZMod (a.toZMod.val * Ray.RAY_SCALE / b.toZMod.val))
  ∨ (U128_MOD ≤ a.toZMod.val * Ray.RAY_SCALE / b.toZMod.val ∨ b.toZMod.val = 0) ∧ ρ.isRight

aegis_prove "wadray::wadray::rdiv_wr" :=
  fun _ _ (a : Wad) (b : Ray) _ (ρ : Wad ⊕ _) => by
  unfold «spec_wadray::wadray::rdiv_wr»
  aesop

aegis_spec "wadray::wadray::wdiv_rw" :=
  fun _ _ (a : Ray) (b : Wad) _ (ρ : Ray ⊕ _) =>
  a.toZMod.val * Wad.WAD_SCALE / b.toZMod.val < U128_MOD
    ∧ b.toZMod.val ≠ 0 ∧ ρ = .inl (Ray.ofZMod (a.toZMod.val * Wad.WAD_SCALE / b.toZMod.val))
  ∨ (U128_MOD ≤ a.toZMod.val * Wad.WAD_SCALE / b.toZMod.val ∨ b.toZMod.val = 0) ∧ ρ.isRight

aegis_prove "wadray::wadray::wdiv_rw" :=
  fun _ _ (a : Ray) (b : Wad) _ (ρ : Wad ⊕ _) => by
  unfold «spec_wadray::wadray::wdiv_rw»
  aesop

aegis_spec "wadray::wadray::rdiv_ww" :=
  fun _ _ (a b : Wad) _ (ρ : Ray ⊕ _) =>
  a.toZMod.val * Ray.RAY_SCALE / b.toZMod.val < U128_MOD
    ∧ b.toZMod.val ≠ 0 ∧ ρ = .inl (Wad.ofZMod (a.toZMod.val * Ray.RAY_SCALE / b.toZMod.val))
  ∨ (U128_MOD ≤ a.toZMod.val * Ray.RAY_SCALE / b.toZMod.val ∨ b.toZMod.val = 0) ∧ ρ.isRight

aegis_prove "wadray::wadray::rdiv_ww" :=
  fun _ _ (a b : Wad) _ (ρ : Ray ⊕ _) => by
  unfold «spec_wadray::wadray::rdiv_ww»
  aesop