{-# LANGUAGE ViewPatterns #-}
module Plutus.Contract.Test.MissingLovelace
  ( calculateDelta
  ) where

import Ledger.Value.CardanoAPI qualified as Value

-- | Returns the calculated delta between initial and final values. Might be false positive.
--
-- The tests check if a wallet's funds are equal to some expected value at the end.
-- Unfortunately, because of the adjustion of transactions, the outputs' costs change
-- and it's hard to track these changes in the tests layer.
--
-- This function tries to check if the difference between final and initial values ('realDelta')
-- is a result of combination of operations between output's costs and the expected delta.
--
-- There is a risk when expected delta has only ada part and expected delta /= realDelta
-- and realDelta is divisible by some delta from deltas, then we will return realDelta's ada.
-- Which means that the test will pass but without strong confidence in wallets' funds consistency.
-- For example, we expected -n, but there is n among deltas and realDelta is n,
-- it is divisible by n, then the test will pass. So please be careful.
calculateDelta
  :: Value.Value
  -- ^ Expected delta of the test
  -> Value.Lovelace
  -- ^ Initial value of the wallet before the test
  -> Value.Lovelace
  -- ^ Final value of the wallet after the test
  -> [Value.Lovelace]
  -- ^ Missing lovelace costs of outputs from 'AdjustingUnbalancedTx' logs
  -> Value.Value
calculateDelta :: Value -> Lovelace -> Lovelace -> [Lovelace] -> Value
calculateDelta Value
expectedDelta Lovelace
initialValue Lovelace
finalValue [Lovelace]
allWalletsTxOutCosts =
  let
    expectedAda :: Lovelace
expectedAda = Value -> Lovelace
Value.selectLovelace Value
expectedDelta

    -- the list of deltas: combinations (+/-) between outputs' costs,
    -- the expected delta and the wallet's output costs.
    deltas :: [Lovelace]
deltas = (Lovelace -> Lovelace) -> [Lovelace] -> [Lovelace]
forall a b. (a -> b) -> [a] -> [b]
map Lovelace -> Lovelace
forall a. Num a => a -> a
abs ([Lovelace] -> [Lovelace]) -> [Lovelace] -> [Lovelace]
forall a b. (a -> b) -> a -> b
$ [[Lovelace]] -> [Lovelace]
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat
      [ [ Lovelace -> Lovelace
forall a. Num a => a -> a
abs Lovelace
val Lovelace -> Lovelace -> Lovelace
forall a. Num a => a -> a -> a
- Lovelace -> Lovelace
forall a. Num a => a -> a
abs Lovelace
wCost
        , Lovelace -> Lovelace
forall a. Num a => a -> a
abs Lovelace
val Lovelace -> Lovelace -> Lovelace
forall a. Num a => a -> a -> a
+ Lovelace -> Lovelace
forall a. Num a => a -> a
abs Lovelace
wCost ] | Lovelace
val <- [Lovelace
expectedAda, Lovelace
0] [Lovelace] -> [Lovelace] -> [Lovelace]
forall a. [a] -> [a] -> [a]
++ [Lovelace]
allWalletsTxOutCosts
                                      , Lovelace
wCost <- [Lovelace]
allWalletsTxOutCosts ]

    realDelta :: Lovelace
realDelta = Lovelace
finalValue Lovelace -> Lovelace -> Lovelace
forall a. Num a => a -> a -> a
- Lovelace
initialValue

    missingDelta :: Value
missingDelta =
      -- We check if 'realDelta' is a result of combination of operations between initial delta and outputs' costs
      -- by checking if 'realDelta''s is divisible by any delta without a reminder.
      if [Bool] -> Bool
forall (t :: * -> *). Foldable t => t Bool -> Bool
or [Lovelace -> Lovelace
forall a. Num a => a -> a
abs Lovelace
realDelta Lovelace -> Lovelace -> Lovelace
forall a. Integral a => a -> a -> a
`mod` Lovelace
d Lovelace -> Lovelace -> Bool
forall a. Eq a => a -> a -> Bool
== Lovelace
0 | Lovelace
d <- [Lovelace]
deltas, Lovelace
d Lovelace -> Lovelace -> Bool
forall a. Eq a => a -> a -> Bool
/= Lovelace
0] then
        -- if yes, we return a sum of 'realDelta''s ada with non-ada value of the expected delta
        let missingAda :: Value
missingAda = Lovelace -> Value
Value.lovelaceToValue Lovelace
realDelta
            missingNonAda :: Value
missingNonAda = Value -> Value
Value.noAdaValue Value
expectedDelta
        in Value
missingAda Value -> Value -> Value
forall a. Semigroup a => a -> a -> a
<> Value
missingNonAda
      -- otherwise we just return the expected delta
      else Value
expectedDelta
  in
    -- if ada in the expected delta is the same as the real delta, then we don't need to check anything
    -- and can just return it
    if Lovelace
expectedAda Lovelace -> Lovelace -> Bool
forall a. Eq a => a -> a -> Bool
== Lovelace
realDelta then Value
expectedDelta
    -- otherwise we return the missing delta that is needed to pass the test
    else Value
missingDelta