//! This module contains the definition of the [`SymbolicValue`] and its
//! supporting types.

pub mod known;

use std::{
    fmt::{Debug, Display, Formatter},
    sync::Arc,
};

use derivative::Derivative;
use uuid::Uuid;

use crate::{
    tc::state::type_variable::TypeVariable,
    utility::clip_uuid,
    vm::value::known::KnownWord,
};

/// The type of auxiliary data used at runtime.
pub type RuntimeAuxData = ();

/// The type of auxiliary data used during type checking.
pub type TCAuxData = TypeVariable;

/// The type of a boxed symbolic value.
pub type BoxedVal<AuxData> = Arc<SymbolicValue<AuxData>>;

/// The type of a boxed value at runtime, where it has no auxiliary data.
pub type RuntimeBoxedVal = BoxedVal<RuntimeAuxData>;

/// The type of a boxed value in the type checker, where it has a type variable
/// as the auxiliary data.
pub type TCBoxedVal = BoxedVal<TCAuxData>;

/// A shorthand form of the symbolic value type, recommended for use when you
/// have to write it out often.
pub type SV<AuxData> = SymbolicValue<AuxData>;

/// A shorthand form of the runtime symbolic value type, for when you have to
/// write it out often.
pub type RSV = SV<RuntimeAuxData>;

/// A shorthand form of the type-checker symbolic value type, for when you have
/// to write it out often.
pub type TCSV = SV<TCAuxData>;

/// A symbolic value is an "execution tree" that records the informative
/// operations that are made to a piece of data. Note that the
/// `instruction_pointer` and `synthetic` flag is ignored for the purposes of
/// equality and hashing.
///
/// # Synthetic Values
///
/// Data is considered to be synthetic when it was generated by the analysis
/// process to more concretely represent an operation.
#[derive(Clone, Debug, Eq, Derivative)]
#[derivative(
    Hash(bound = "AuxData: std::hash::Hash"),
    PartialEq(bound = "AuxData: std::cmp::PartialEq")
)]
pub struct SymbolicValue<AuxData> {
    /// The instruction pointer's value at the location where this part of the
    /// symbolic execution tree was recorded.
    #[derivative(PartialEq = "ignore", Hash = "ignore")]
    instruction_pointer: u32,

    /// Where the data at this level came from.
    #[derivative(PartialEq = "ignore", Hash = "ignore")]
    provenance: Provenance,

    /// The actual execution tree that forms this value.
    data: SymbolicValueData<AuxData>,

    /// Auxiliary data to be included with the value.
    aux_data: AuxData,

    /// The number of nodes in the tree beginning at the node in `data`.
    ///
    /// Note that this value _includes_ the node that it is being called on in
    /// the total count.
    size: usize,
}

impl<AuxData> SymbolicValue<AuxData>
where
    AuxData: Clone + PartialEq,
{
    /// Gets the instruction pointer value for the location where this portion
    /// of the value was constructed.
    #[must_use]
    pub fn instruction_pointer(&self) -> u32 {
        self.instruction_pointer
    }

    /// Gets the provenance for the value, namely where it came from during
    /// execution.
    #[must_use]
    pub fn provenance(&self) -> Provenance {
        self.provenance
    }

    /// Gets the execution tree that makes up this value.
    #[must_use]
    pub fn data(&self) -> &SymbolicValueData<AuxData> {
        &self.data
    }

    /// Gets the auxiliary data associated with this value.
    #[must_use]
    pub fn aux_data(&self) -> &AuxData {
        &self.aux_data
    }

    /// Gets the number of nodes in the tree beginning at the node in `data`.
    ///
    /// Note that this value _includes_ the node that it is being called on in
    /// the total count.
    #[must_use]
    pub fn size(&self) -> usize {
        self.size
    }

    /// Consumes the symbolic value, returning its data and auxiliary data
    /// payloads.
    #[must_use]
    pub fn consume(self) -> ValuePayload<AuxData> {
        ValuePayload::new(self.data, self.aux_data)
    }

    /// Compares two symbolic values for strict equality, _including_ the value
    /// of the `instruction_pointer`.
    #[must_use]
    pub fn strict_eq(&self, other: &Self) -> bool {
        self.instruction_pointer == other.instruction_pointer && self == other
    }

    /// Performs constant folding on immediates wherever possible.
    ///
    /// This means that it will go through the entire value to find any
    /// opportunities where all the operands to a node are
    /// [`SymbolicValueData::KnownData`]s that can be combined statically.
    ///
    /// # Note
    ///
    /// This algorithm is recursive. If it turns out to be a problem in practice
    /// it can be re-written.
    #[must_use]
    pub fn constant_fold(&self) -> Arc<Self> {
        let data = self.data.constant_fold();
        let instruction_pointer = self.instruction_pointer;
        let provenance = self.provenance;
        let aux_data = self.aux_data.clone();
        let size = data.child_size() + 1;

        Arc::new(Self {
            instruction_pointer,
            provenance,
            data,
            aux_data,
            size,
        })
    }

    /// Checks if the payload is known data.
    #[must_use]
    pub fn is_known_data(&self) -> bool {
        matches!(self.data, SymbolicValueData::KnownData { .. })
    }

    /// Converts the payload into a VM word if possible.
    #[must_use]
    pub fn as_word(&self) -> Option<KnownWord> {
        match &self.data {
            SymbolicValueData::KnownData { value } => Some(*value),
            _ => None,
        }
    }

    /// Transforms the payload data of the symbolic value by processing
    /// `self.data` with the `transform`.
    #[must_use]
    pub fn transform_data(
        &self,
        transform: impl Fn(&SymbolicValueData<AuxData>) -> Option<SymbolicValueData<AuxData>> + Copy,
    ) -> Arc<Self> {
        let data = self.data.transform(transform);
        let size = data.child_size() + 1;
        Arc::new(Self {
            instruction_pointer: self.instruction_pointer,
            data,
            provenance: self.provenance,
            aux_data: self.aux_data.clone(),
            size,
        })
    }

    /// Gets all of the child nodes of this node.
    #[allow(clippy::borrowed_box)] // We work with these things boxed everywhere
    #[must_use]
    pub fn children(&self) -> Vec<Arc<Self>> {
        self.data.children()
    }
}

impl RSV {
    /// Constructs a new, `SymbolicValue` representing the operation performed
    /// at `instruction_pointer` on the symbolic `data` and with the specified
    /// `provenance`.
    ///
    /// It returns [`RuntimeBoxedVal`] as in the vast majority of cases this
    /// type is used in a recursive data type and hence indirection is
    /// needed.
    ///
    /// # Size Limits
    ///
    /// The size of the value tree being constructed is checked against the
    /// `value_size_limit` parameter. If it exceeds that limit, the provided
    /// `data` is ignored, instead being replaced by a [`RSVD::Value`] to give
    /// the new tree a size of 1.
    ///
    /// This does throw data away, which has the potential to impact type
    /// checking operations later.
    ///
    /// If you want to disable this behaviour, set `value_size_limit` to
    /// [`None`].
    #[must_use]
    pub fn new(
        instruction_pointer: u32,
        data: RSVD,
        provenance: Provenance,
        value_size_limit: Option<usize>,
    ) -> RuntimeBoxedVal {
        let size = data.child_size() + 1;
        let data = if let Some(limit) = value_size_limit {
            if size > limit {
                RSVD::new_value()
            } else {
                data
            }
        } else {
            data
        };

        Arc::new(Self {
            instruction_pointer,
            provenance,
            data,
            aux_data: (),
            size,
        })
    }

    /// Constructs a new `SymbolicValue` representing the operation performed at
    /// `instruction_pointer` on the symbolic `data`. It is created with a
    /// provenance of [`Provenance::Execution`] to indicate that the symbolic
    /// execution of the program created the value.
    ///
    /// It returns [`RuntimeBoxedVal`] as in the vast majority of cases this
    /// type is used in a recursive data type and hence indirection is
    /// needed.
    ///
    /// # Size Limits
    ///
    /// The size of the value tree being constructed is checked against the
    /// `value_size_limit` parameter. If it exceeds that limit, the provided
    /// `data` is ignored, instead being replaced by a [`RSVD::Value`] to give
    /// the new tree a size of 1.
    ///
    /// This does throw data away, which has the potential to impact type
    /// checking operations later.
    ///
    /// If you want to disable this behaviour, set `value_size_limit` to
    /// [`None`].
    #[must_use]
    pub fn new_from_execution(
        instruction_pointer: u32,
        data: RSVD,
        value_size_limit: Option<usize>,
    ) -> RuntimeBoxedVal {
        Self::new(
            instruction_pointer,
            data,
            Provenance::Execution,
            value_size_limit,
        )
    }

    /// Constructs a new, synthetic, `SymbolicValue` representing the operation
    /// performed at `instruction_pointer` on the symbolic `data`. It is created
    /// with a provenance of [`Provenance::Synthetic`] to indicate that it was
    /// generated by the analysis process.
    ///
    /// It returns [`RuntimeBoxedVal`] as in the vast majority of cases this
    /// type is used in a recursive data type and hence indirection is
    /// needed.
    #[must_use]
    pub fn new_synthetic(instruction_pointer: u32, data: RSVD) -> RuntimeBoxedVal {
        Self::new(instruction_pointer, data, Provenance::Synthetic, None)
    }

    /// Constructs a new `SymbolicValue` representing a symbolic value created
    /// at `instruction_pointer` with the provided `provenance`.
    ///
    /// It returns [`RuntimeBoxedVal`] as in the vast majority of cases this
    /// type is used in a recursive data type and hence indirection is
    /// needed.
    #[must_use]
    pub fn new_value(instruction_pointer: u32, provenance: Provenance) -> RuntimeBoxedVal {
        Self::new(
            instruction_pointer,
            SymbolicValueData::new_value(),
            provenance,
            None,
        )
    }

    /// Constructs a new `SymbolicValue` representing a known value of
    /// `value_data` created at `instruction_pointer` with the specified
    /// `provenance`.
    ///
    /// It returns [`RuntimeBoxedVal`] as in the vast majority of cases this
    /// type is used in a recursive data type and hence indirection is
    /// needed.
    ///
    /// # Size Limits
    ///
    /// The size of the value tree being constructed is checked against the
    /// `value_size_limit` parameter. If it exceeds that limit, the provided
    /// `data` is ignored, instead being replaced by a [`RSVD::Value`] to give
    /// the new tree a size of 1.
    ///
    /// This does throw data away, which has the potential to impact type
    /// checking operations later.
    ///
    /// If you want to disable this behaviour, set `value_size_limit` to
    /// [`None`].
    #[must_use]
    pub fn new_known_value(
        instruction_pointer: u32,
        value_data: KnownWord,
        provenance: Provenance,
        value_size_limit: Option<usize>,
    ) -> RuntimeBoxedVal {
        Self::new(
            instruction_pointer,
            SymbolicValueData::KnownData { value: value_data },
            provenance,
            value_size_limit,
        )
    }
}

impl TCSV {
    /// Constructs a new `SymbolicValue` representing the operation performed at
    /// `instruction_pointer` on the symbolic `data` with the specified
    /// `provenance` and provided `aux_data`.
    ///
    /// It returns [`TCBoxedVal`] as in the vast majority of cases this type is
    /// used in a recursive data type and hence indirection is needed.
    #[must_use]
    pub fn new(
        instruction_pointer: u32,
        data: TCSVD,
        provenance: Provenance,
        aux_data: TCAuxData,
    ) -> TCBoxedVal {
        let size = data.child_size() + 1;
        Arc::new(Self {
            instruction_pointer,
            provenance,
            data,
            aux_data,
            size,
        })
    }

    /// Gets the type variable associated with this type-checker symbolic value.
    #[must_use]
    pub fn type_var(&self) -> TypeVariable {
        self.aux_data
    }
}

impl<AuxData> Display for SymbolicValue<AuxData>
where
    AuxData: Debug,
{
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.data)
    }
}

/// An alias recommended for use when you have to write it out often.
pub type SVD<AuxData> = SymbolicValueData<AuxData>;

/// The type of symbolic value data used at runtime.
pub type RSVD = SVD<RuntimeAuxData>;

/// The type of symbolic value data used during type checking.
pub type TCSVD = SVD<TCAuxData>;

/// The execution tree structures that allow the executor to build traces of the
/// execution pertaining to certain symbolic values.
///
/// Note that these do not duplicate the opcodes 1:1, instead representing the
/// opcode operations that _provide information about the type of a value_ as an
/// execution tree. Notable (and intentional) omissions here are the opcodes
/// that deal with memory, storage, and the stack, as these are instead
/// mechanisms by which values get moved and manipulated at runtime.
///
/// # Semantics
///
/// For the semantics of these operations at runtime, please see the
/// corresponding documentation comments in the [`crate::opcode`] subtree.
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub enum SymbolicValueData<AuxData> {
    /// A value with identity, but about which nothing else is known.
    Value { id: Uuid },

    /// A value that has is made up of a known sequence of bytes.
    KnownData { value: KnownWord },

    /// Addition of symbolic values.
    Add { left: BoxedVal<AuxData>, right: BoxedVal<AuxData> },

    /// Multiplication of symbolic values.
    Multiply { left: BoxedVal<AuxData>, right: BoxedVal<AuxData> },

    /// Subtraction of symbolic values.
    Subtract { left: BoxedVal<AuxData>, right: BoxedVal<AuxData> },

    /// Division of symbolic values.
    Divide { dividend: BoxedVal<AuxData>, divisor: BoxedVal<AuxData> },

    /// Signed division of symbolic values.
    SignedDivide { dividend: BoxedVal<AuxData>, divisor: BoxedVal<AuxData> },

    /// Modulo of symbolic values.
    Modulo { dividend: BoxedVal<AuxData>, divisor: BoxedVal<AuxData> },

    /// Signed modulo of symbolic values.
    SignedModulo { dividend: BoxedVal<AuxData>, divisor: BoxedVal<AuxData> },

    /// Exponentiation of symbolic values.
    Exp { value: BoxedVal<AuxData>, exponent: BoxedVal<AuxData> },

    /// Sign extension of a symbolic value to a symbolic length.
    SignExtend { size: BoxedVal<AuxData>, value: BoxedVal<AuxData> },

    /// A message call that passes a value.
    ///
    /// We explicitly _do not care_ about the specific kind of call here, as it
    /// makes no difference to the kind of information we get.
    ///
    /// We also do not store the return data in the opcode, as we know nothing
    /// about what the call will do with the argument. Internal execution will
    /// read from memory directly, so we pick up the returned value there.
    CallWithValue {
        gas:           BoxedVal<AuxData>,
        address:       BoxedVal<AuxData>,
        value:         BoxedVal<AuxData>,
        argument_data: BoxedVal<AuxData>,
        ret_offset:    BoxedVal<AuxData>,
        ret_size:      BoxedVal<AuxData>,
    },

    /// A message call that passes a value.
    ///
    /// We explicitly _do not care_ about the specific kind of call here, as it
    /// makes no difference to the kind of information we get.
    ///
    /// We also do not store the return data in the opcode, as we know nothing
    /// about what the call will do with the argument. Internal execution will
    /// read from memory directly, so we pick up the returned value there.
    CallWithoutValue {
        gas:           BoxedVal<AuxData>,
        address:       BoxedVal<AuxData>,
        argument_data: BoxedVal<AuxData>,
        ret_offset:    BoxedVal<AuxData>,
        ret_size:      BoxedVal<AuxData>,
    },

    /// A keccak256 hash on symbolic values.
    Sha3 { data: BoxedVal<AuxData> },

    /// The address of the currently-executing contract.
    Address,

    /// The balance of the target account.
    Balance { address: BoxedVal<AuxData> },

    /// The address of the transaction's origin.
    Origin,

    /// The caller of the transaction.
    Caller,

    /// The value deposited by the caller.
    CallValue,

    /// The current gas price.
    GasPrice,

    /// Compute the external code hash of a symbolic value.
    ExtCodeHash { address: BoxedVal<AuxData> },

    /// Gets the block hash from one of the
    BlockHash { block_number: BoxedVal<AuxData> },

    /// Gets the block's beneficiary address.
    CoinBase,

    /// Gets the timestamp of the current block.
    BlockTimestamp,

    /// Gets the number of the current block.
    BlockNumber,

    /// Gets the difficulty of the current block.
    Prevrandao,

    /// Gets the gas limit of  the current block.
    GasLimit,

    /// Gets the identifier for the chain on which the current block is
    /// executing.
    ChainId,

    /// Gets the balance of the currently executing account.
    SelfBalance,

    /// Gets the block base fee.
    BaseFee,

    /// Gets the currently available gas.
    Gas,

    /// Logs the `value` from memory of `size` with the provided `topics`.
    Log { data: BoxedVal<AuxData>, topics: Vec<BoxedVal<AuxData>> },

    /// Creates a new contract.
    Create { value: BoxedVal<AuxData>, data: BoxedVal<AuxData> },

    /// Creates a new contract at a predictable address.
    Create2 {
        value: BoxedVal<AuxData>,
        salt:  BoxedVal<AuxData>,
        data:  BoxedVal<AuxData>,
    },

    /// Registers the account for deletion.
    SelfDestruct { target: BoxedVal<AuxData> },

    /// Less than for symbolic values.
    LessThan { left: BoxedVal<AuxData>, right: BoxedVal<AuxData> },

    /// Greater than for symbolic values.
    GreaterThan { left: BoxedVal<AuxData>, right: BoxedVal<AuxData> },

    /// Less than for symbolic values where the values are signed.
    SignedLessThan { left: BoxedVal<AuxData>, right: BoxedVal<AuxData> },

    /// Greater than for symbolic values where the values are signed.
    SignedGreaterThan { left: BoxedVal<AuxData>, right: BoxedVal<AuxData> },

    /// Equality for symbolic values.
    Equals { left: BoxedVal<AuxData>, right: BoxedVal<AuxData> },

    /// Checking if a symbolic value is zero.
    IsZero { number: BoxedVal<AuxData> },

    /// Logical conjunction for symbolic values.
    And { left: BoxedVal<AuxData>, right: BoxedVal<AuxData> },

    /// Logical disjunction for symbolic values.
    Or { left: BoxedVal<AuxData>, right: BoxedVal<AuxData> },

    /// XOR for symbolic values.
    Xor { left: BoxedVal<AuxData>, right: BoxedVal<AuxData> },

    /// Negation of a symbolic value.
    Not { value: BoxedVal<AuxData> },

    /// Left shift with symbolic values.
    LeftShift { shift: BoxedVal<AuxData>, value: BoxedVal<AuxData> },

    /// Right shift with symbolic values.
    RightShift { shift: BoxedVal<AuxData>, value: BoxedVal<AuxData> },

    /// Signed right shift with symbolic values.
    ArithmeticRightShift { shift: BoxedVal<AuxData>, value: BoxedVal<AuxData> },

    /// Loading the data at `offset` for `size` in the call data.
    ///
    /// Note that CallData has non-structural identity.
    CallData { id: Uuid, offset: BoxedVal<AuxData>, size: BoxedVal<AuxData> },

    /// The size of the current call data.
    CallDataSize,

    /// Data copied from the code of the current contract starting at `offset`
    /// for `size`.
    CodeCopy { offset: BoxedVal<AuxData>, size: BoxedVal<AuxData> },

    /// Gets the code size of the target contract.
    ExtCodeSize { address: BoxedVal<AuxData> },

    /// Data copied from the code of the contract at `address` starting at
    /// `offset` for `size`.
    ExtCodeCopy {
        address: BoxedVal<AuxData>,
        offset:  BoxedVal<AuxData>,
        size:    BoxedVal<AuxData>,
    },

    /// Data copied from the return data from the previous call at `offset` for
    /// `size`.
    ReturnData { offset: BoxedVal<AuxData>, size: BoxedVal<AuxData> },

    /// The return. Does not stay on the stack but is stored nevertheless.
    Return { data: BoxedVal<AuxData> },

    /// The revert. Does not stay on the stack but is stored nevertheless.
    Revert { data: BoxedVal<AuxData> },

    /// The value read from storage that has not been written to at the time of
    /// loading during the course of execution in the virtual machine.
    UnwrittenStorageValue { key: BoxedVal<AuxData> },

    /// A value representing the return from a storage load at `key`.
    ///
    /// If there is no value in the storage slot for `key`, `value` will be
    /// [`Self::UnwrittenStorageValue`].
    SLoad { key: BoxedVal<AuxData>, value: BoxedVal<AuxData> },

    /// A storage slot at `key`.
    StorageSlot { key: BoxedVal<AuxData> },

    /// A representation of the storing of `value` at `key` in storage
    StorageWrite { key: BoxedVal<AuxData>, value: BoxedVal<AuxData> },

    /// The concatenation of multiple values.
    Concat { values: Vec<BoxedVal<AuxData>> },

    /// The value is a mapping index with `slot` as its base and the particular
    /// location specified by `key` offset by `projection`.
    MappingIndex {
        slot:       BoxedVal<AuxData>,
        key:        BoxedVal<AuxData>,
        projection: Option<usize>,
    },

    /// The value is a dynamic array access for the array with its length stored
    /// at `slot` and the `index` specifying the element in the array.
    DynamicArrayIndex { slot: BoxedVal<AuxData>, index: BoxedVal<AuxData> },

    /// An operation that masks `value` to construct a sub-word value.
    ///
    /// The sub-word value begins at `offset` (0-based in bits where 0 is the
    /// LSB) in the overarching word, and with `size` (in bits).
    SubWord { value: BoxedVal<AuxData>, offset: usize, size: usize },

    /// An operation that shifts `value` to begin at `offset` within the
    /// containing word.
    ///
    /// Note that `offset` is the 0-indexed bit where the value begins.
    Shifted { offset: usize, value: BoxedVal<AuxData> },

    /// A packed encoding of data as a value, containing `elements`.
    Packed { elements: Vec<PackedSpan<AuxData>> },
}

impl<AuxData> SymbolicValueData<AuxData> {
    /// Constructs a new [`Self::KnownData`] containing the data `value`.
    #[must_use]
    pub fn new_known(value: KnownWord) -> Self {
        SymbolicValueData::KnownData { value }
    }

    /// Constructs a new [`Self::Value`] about which only its existence and
    /// identity are known.
    #[must_use]
    pub fn new_value() -> Self {
        let id = Uuid::new_v4();
        SymbolicValueData::Value { id }
    }

    /// Constructs a new [`Self::CallData`] instance with identity.
    #[must_use]
    pub fn call_data(offset: BoxedVal<AuxData>, size: BoxedVal<AuxData>) -> Self {
        let id = Uuid::new_v4();
        Self::CallData { id, offset, size }
    }
}

impl<AuxData> SymbolicValueData<AuxData>
where
    AuxData: Clone + PartialEq,
{
    /// Gets the number of nodes in the tree that are children of `self`, not
    /// including `self` in the count.
    ///
    /// Computing this value does _not_ require traversing the entire tree as
    /// the value is memoized in [`SymbolicValue`] instead. This _does_ mean
    /// that transformations of symbolic values must recompute the result.
    ///
    /// If you want to compute this value dynamically, instead recursively call
    /// [`Self::children`] and get the size of the result.
    #[allow(clippy::match_same_arms)] // Merging doesn't make sense here
    #[must_use]
    pub fn child_size(&self) -> usize {
        match self {
            SVD::Value { .. } => 0,
            SVD::KnownData { .. } => 0,
            SVD::Add { left, right } => left.size() + right.size(),
            SVD::Multiply { left, right } => left.size() + right.size(),
            SVD::Subtract { left, right } => left.size() + right.size(),
            SVD::Divide { dividend, divisor } => dividend.size() + divisor.size(),
            SVD::SignedDivide { dividend, divisor } => dividend.size() + divisor.size(),
            SVD::Modulo { dividend, divisor } => dividend.size() + divisor.size(),
            SVD::SignedModulo { dividend, divisor } => dividend.size() + divisor.size(),
            SVD::Exp { value, exponent } => value.size() + exponent.size(),
            SVD::SignExtend { size, value } => size.size() + value.size(),
            SVD::CallWithValue {
                gas,
                address,
                value,
                argument_data,
                ret_offset,
                ret_size,
            } => {
                gas.size()
                    + address.size()
                    + value.size()
                    + argument_data.size()
                    + ret_offset.size()
                    + ret_size.size()
            }
            SVD::CallWithoutValue {
                gas,
                address,
                argument_data,
                ret_offset,
                ret_size,
            } => {
                gas.size()
                    + address.size()
                    + argument_data.size()
                    + ret_offset.size()
                    + ret_size.size()
            }
            SVD::Sha3 { data } => data.size(),
            SVD::Address => 0,
            SVD::Balance { address } => address.size(),
            SVD::Origin => 0,
            SVD::Caller => 0,
            SVD::CallValue => 0,
            SVD::GasPrice => 0,
            SVD::ExtCodeHash { address } => address.size(),
            SVD::BlockHash { block_number } => block_number.size(),
            SVD::CoinBase => 0,
            SVD::BlockTimestamp => 0,
            SVD::BlockNumber => 0,
            SVD::Prevrandao => 0,
            SVD::GasLimit => 0,
            SVD::ChainId => 0,
            SVD::SelfBalance => 0,
            SVD::BaseFee => 0,
            SVD::Gas => 0,
            SVD::Log { data, topics } => {
                data.size() + topics.iter().map(|t| t.size()).sum::<usize>()
            }
            SVD::Create { value, data } => value.size() + data.size(),
            SVD::Create2 { value, salt, data } => value.size() + salt.size() + data.size(),
            SVD::SelfDestruct { target } => target.size(),
            SVD::LessThan { left, right } => left.size() + right.size(),
            SVD::GreaterThan { left, right } => left.size() + right.size(),
            SVD::SignedLessThan { left, right } => left.size() + right.size(),
            SVD::SignedGreaterThan { left, right } => left.size() + right.size(),
            SVD::Equals { left, right } => left.size() + right.size(),
            SVD::IsZero { number } => number.size(),
            SVD::And { left, right } => left.size() + right.size(),
            SVD::Or { left, right } => left.size() + right.size(),
            SVD::Xor { left, right } => left.size() + right.size(),
            SVD::Not { value } => value.size(),
            SVD::LeftShift { shift, value } => shift.size() + value.size(),
            SVD::RightShift { shift, value } => shift.size() + value.size(),
            SVD::ArithmeticRightShift { shift, value } => shift.size() + value.size(),
            SVD::CallData { offset, size, .. } => offset.size() + size.size(),
            SVD::CallDataSize => 0,
            SVD::CodeCopy { offset, size } => offset.size() + size.size(),
            SVD::ExtCodeSize { address } => address.size(),
            SVD::ExtCodeCopy {
                address,
                offset,
                size,
            } => address.size() + offset.size() + size.size(),
            SVD::ReturnData { offset, size } => offset.size() + size.size(),
            SVD::Return { data } => data.size(),
            SVD::Revert { data } => data.size(),
            SVD::UnwrittenStorageValue { key } => key.size(),
            SVD::SLoad { key, value } => key.size() + value.size(),
            SVD::StorageSlot { key } => key.size(),
            SVD::StorageWrite { key, value } => key.size() + value.size(),
            SVD::Concat { values } => values.iter().map(|v| v.size()).sum(),
            SVD::MappingIndex { slot, key, .. } => slot.size() + key.size(),
            SVD::DynamicArrayIndex { slot, index } => slot.size() + index.size(),
            SVD::SubWord { value, .. } => value.size(),
            SVD::Shifted { value, .. } => value.size(),
            SVD::Packed { elements } => elements.iter().map(|s| s.value.size()).sum(),
        }
    }

    /// Transforms the data payload by applying the provided `transform` to it.
    ///
    /// It operates recursively on the entire tree, applying the transformation
    /// at the first opportunity. If you do not write `transform` carefully,
    /// this means that it may short-circuit other opportunities for
    /// transformation.
    ///
    /// In other words, you want to have transform recurse in many cases when it
    /// processes.
    ///
    /// # Recursion
    ///
    /// This algorithm is recursive. If it turns out to be a problem in practice
    /// it can be re-written.
    #[allow(clippy::match_same_arms, clippy::too_many_lines)]
    #[must_use]
    pub fn transform(&self, transform: impl Fn(&Self) -> Option<Self> + Copy) -> Self {
        let inner_self = self.clone();
        match transform(&inner_self) {
            Some(data) => data,
            None => match self {
                Self::Value { .. } => inner_self,
                Self::KnownData { .. } => inner_self,
                Self::Add { left, right } => Self::Add {
                    left:  left.transform_data(transform),
                    right: right.transform_data(transform),
                },
                Self::Multiply { left, right } => Self::Multiply {
                    left:  left.transform_data(transform),
                    right: right.transform_data(transform),
                },
                Self::Subtract { left, right } => Self::Subtract {
                    left:  left.transform_data(transform),
                    right: right.transform_data(transform),
                },
                Self::Divide { divisor, dividend } => Self::Divide {
                    dividend: dividend.transform_data(transform),
                    divisor:  divisor.transform_data(transform),
                },
                Self::SignedDivide { divisor, dividend } => Self::SignedDivide {
                    dividend: dividend.transform_data(transform),
                    divisor:  divisor.transform_data(transform),
                },
                Self::Modulo { divisor, dividend } => Self::Modulo {
                    dividend: dividend.transform_data(transform),
                    divisor:  divisor.transform_data(transform),
                },
                Self::SignedModulo { divisor, dividend } => Self::SignedModulo {
                    dividend: dividend.transform_data(transform),
                    divisor:  divisor.transform_data(transform),
                },
                Self::Exp { value, exponent } => Self::Exp {
                    value:    value.transform_data(transform),
                    exponent: exponent.transform_data(transform),
                },
                Self::SignExtend { size, value } => Self::SignExtend {
                    size:  size.transform_data(transform),
                    value: value.transform_data(transform),
                },
                Self::CallWithValue {
                    gas,
                    address,
                    value,
                    argument_data,
                    ret_offset,
                    ret_size,
                } => Self::CallWithValue {
                    gas:           gas.transform_data(transform),
                    address:       address.transform_data(transform),
                    value:         value.transform_data(transform),
                    argument_data: argument_data.transform_data(transform),
                    ret_offset:    ret_offset.transform_data(transform),
                    ret_size:      ret_size.transform_data(transform),
                },
                Self::CallWithoutValue {
                    gas,
                    address,
                    argument_data,
                    ret_offset,
                    ret_size,
                } => Self::CallWithoutValue {
                    gas:           gas.transform_data(transform),
                    address:       address.transform_data(transform),
                    argument_data: argument_data.transform_data(transform),
                    ret_offset:    ret_offset.transform_data(transform),
                    ret_size:      ret_size.transform_data(transform),
                },
                Self::Sha3 { data } => Self::Sha3 {
                    data: data.transform_data(transform),
                },
                Self::Address => inner_self,
                Self::Balance { address } => Self::Balance {
                    address: address.transform_data(transform),
                },
                Self::Origin => inner_self,
                Self::Caller => inner_self,
                Self::CallValue => inner_self,
                Self::GasPrice => inner_self,
                Self::ExtCodeHash { address } => Self::ExtCodeHash {
                    address: address.transform_data(transform),
                },
                Self::BlockHash { block_number } => Self::BlockHash {
                    block_number: block_number.transform_data(transform),
                },
                Self::CoinBase => inner_self,
                Self::BlockTimestamp => inner_self,
                Self::BlockNumber => inner_self,
                Self::Prevrandao => inner_self,
                Self::GasLimit => inner_self,
                Self::ChainId => inner_self,
                Self::SelfBalance => inner_self,
                Self::BaseFee => inner_self,
                Self::Gas => inner_self,
                Self::Log { data, topics } => Self::Log {
                    data:   data.transform_data(transform),
                    topics: topics.iter().map(|t| t.transform_data(transform)).collect(),
                },
                Self::Create { value, data } => Self::Create {
                    value: value.transform_data(transform),
                    data:  data.transform_data(transform),
                },
                Self::Create2 { value, data, salt } => Self::Create2 {
                    value: value.transform_data(transform),
                    data:  data.transform_data(transform),
                    salt:  salt.transform_data(transform),
                },
                Self::SelfDestruct { target } => Self::SelfDestruct {
                    target: target.transform_data(transform),
                },
                Self::LessThan { left, right } => Self::LessThan {
                    left:  left.transform_data(transform),
                    right: right.transform_data(transform),
                },
                Self::GreaterThan { left, right } => Self::GreaterThan {
                    left:  left.transform_data(transform),
                    right: right.transform_data(transform),
                },
                Self::SignedLessThan { left, right } => Self::SignedLessThan {
                    left:  left.transform_data(transform),
                    right: right.transform_data(transform),
                },
                Self::SignedGreaterThan { left, right } => Self::SignedGreaterThan {
                    left:  left.transform_data(transform),
                    right: right.transform_data(transform),
                },
                Self::Equals { left, right } => Self::Equals {
                    left:  left.transform_data(transform),
                    right: right.transform_data(transform),
                },
                Self::IsZero { number } => Self::IsZero {
                    number: number.transform_data(transform),
                },
                Self::And { left, right } => Self::And {
                    left:  left.transform_data(transform),
                    right: right.transform_data(transform),
                },
                Self::Or { left, right } => Self::Or {
                    left:  left.transform_data(transform),
                    right: right.transform_data(transform),
                },
                Self::Xor { left, right } => Self::Xor {
                    left:  left.transform_data(transform),
                    right: right.transform_data(transform),
                },
                Self::Not { value } => Self::Not {
                    value: value.transform_data(transform),
                },
                Self::LeftShift { shift, value } => Self::LeftShift {
                    shift: shift.transform_data(transform),
                    value: value.transform_data(transform),
                },
                Self::RightShift { shift, value } => Self::RightShift {
                    shift: shift.transform_data(transform),
                    value: value.transform_data(transform),
                },
                Self::ArithmeticRightShift { shift, value } => Self::ArithmeticRightShift {
                    shift: shift.transform_data(transform),
                    value: value.transform_data(transform),
                },
                Self::CallData { id, offset, size } => Self::CallData {
                    id:     *id,
                    offset: offset.transform_data(transform),
                    size:   size.transform_data(transform),
                },
                Self::CallDataSize => inner_self,
                Self::CodeCopy { offset, size } => Self::CodeCopy {
                    offset: offset.transform_data(transform),
                    size:   size.transform_data(transform),
                },
                Self::ExtCodeSize { address } => Self::ExtCodeSize {
                    address: address.transform_data(transform),
                },
                Self::ExtCodeCopy {
                    address,
                    offset,
                    size,
                } => Self::ExtCodeCopy {
                    address: address.transform_data(transform),
                    offset:  offset.transform_data(transform),
                    size:    size.transform_data(transform),
                },
                Self::ReturnData { offset, size } => Self::ReturnData {
                    offset: offset.transform_data(transform),
                    size:   size.transform_data(transform),
                },
                Self::Return { data } => Self::Return {
                    data: data.transform_data(transform),
                },
                Self::Revert { data } => Self::Revert {
                    data: data.transform_data(transform),
                },
                Self::UnwrittenStorageValue { key } => Self::UnwrittenStorageValue {
                    key: key.transform_data(transform),
                },
                Self::SLoad { key, value } => Self::SLoad {
                    key:   key.transform_data(transform),
                    value: value.transform_data(transform),
                },
                Self::StorageSlot { key } => Self::StorageSlot {
                    key: key.transform_data(transform),
                },
                Self::StorageWrite { key, value } => Self::StorageWrite {
                    key:   key.transform_data(transform),
                    value: value.transform_data(transform),
                },
                Self::Concat { values } => Self::Concat {
                    values: values.iter().map(|v| v.transform_data(transform)).collect(),
                },
                Self::MappingIndex {
                    slot,
                    key,
                    projection,
                } => Self::MappingIndex {
                    slot:       slot.transform_data(transform),
                    key:        key.transform_data(transform),
                    projection: *projection,
                },
                Self::DynamicArrayIndex { slot, index } => Self::DynamicArrayIndex {
                    slot:  slot.transform_data(transform),
                    index: index.transform_data(transform),
                },
                Self::SubWord {
                    value,
                    offset,
                    size,
                } => Self::SubWord {
                    value:  value.transform_data(transform),
                    offset: *offset,
                    size:   *size,
                },
                Self::Shifted { offset, value } => Self::Shifted {
                    offset: *offset,
                    value:  value.transform_data(transform),
                },
                Self::Packed { elements } => Self::Packed {
                    elements: elements.iter().map(|elem| elem.transform(transform)).collect(),
                },
            },
        }
    }

    /// Performs constant folding on immediate values wherever possible.
    ///
    /// This means that it will go through the entire value to find any
    /// opportunities where all the operands to a node are [`Self::KnownData`]
    /// such that the value of that node can be computed statically.
    ///
    /// # Recursion
    ///
    /// This algorithm is recursive. If it turns out to be a problem in practice
    /// it can be re-written.
    #[allow(clippy::match_same_arms, clippy::too_many_lines)]
    #[must_use]
    pub fn constant_fold(&self) -> Self {
        // The inner definition that actually implements the constant folding operation.
        #[allow(clippy::too_many_lines)]
        fn constant_folder<AuxData>(data: &SVD<AuxData>) -> Option<SVD<AuxData>>
        where
            AuxData: Clone + PartialEq,
        {
            match data.clone() {
                SVD::Add { left, right } => {
                    let left = left.transform_data(constant_folder);
                    let right = right.transform_data(constant_folder);
                    Some(match (left.as_word(), right.as_word()) {
                        (Some(a), Some(b)) => SVD::new_known(a + b),
                        _ => SVD::Add { left, right },
                    })
                }
                SVD::Multiply { left, right } => {
                    let left = left.transform_data(constant_folder);
                    let right = right.transform_data(constant_folder);
                    Some(match (left.as_word(), right.as_word()) {
                        (Some(a), Some(b)) => SVD::new_known(a * b),
                        _ => SVD::Add { left, right },
                    })
                }
                SVD::Subtract { left, right } => {
                    let left = left.transform_data(constant_folder);
                    let right = right.transform_data(constant_folder);
                    Some(match (left.as_word(), right.as_word()) {
                        (Some(a), Some(b)) => SVD::new_known(a - b),
                        _ => SVD::Subtract { left, right },
                    })
                }
                SVD::Divide { divisor, dividend } => {
                    let divisor = divisor.transform_data(constant_folder);
                    let dividend = dividend.transform_data(constant_folder);
                    Some(match (dividend.as_word(), divisor.as_word()) {
                        (Some(a), Some(b)) => SVD::new_known(a / b),
                        _ => SVD::Divide { dividend, divisor },
                    })
                }
                SVD::SignedDivide { dividend, divisor } => {
                    let divisor = divisor.transform_data(constant_folder);
                    let dividend = dividend.transform_data(constant_folder);
                    Some(match (dividend.as_word(), divisor.as_word()) {
                        (Some(a), Some(b)) => SVD::new_known(a.signed_div(b)),
                        _ => SVD::SignedDivide { dividend, divisor },
                    })
                }
                SVD::Modulo { divisor, dividend } => {
                    let divisor = divisor.transform_data(constant_folder);
                    let dividend = dividend.transform_data(constant_folder);
                    Some(match (dividend.as_word(), divisor.as_word()) {
                        (Some(a), Some(b)) => SVD::new_known(a % b),
                        _ => SVD::Modulo { dividend, divisor },
                    })
                }
                SVD::SignedModulo { dividend, divisor } => {
                    let divisor = divisor.transform_data(constant_folder);
                    let dividend = dividend.transform_data(constant_folder);
                    Some(match (dividend.as_word(), divisor.as_word()) {
                        (Some(a), Some(b)) => SVD::new_known(a.signed_rem(b)),
                        _ => SVD::SignedDivide { dividend, divisor },
                    })
                }
                SVD::Exp { value, exponent } => {
                    let value = value.transform_data(constant_folder);
                    let exponent = exponent.transform_data(constant_folder);
                    Some(match (value.as_word(), exponent.as_word()) {
                        (Some(a), Some(b)) => SVD::new_known(a.exp(b)),
                        _ => SVD::Exp { value, exponent },
                    })
                }
                SVD::LessThan { left, right } => {
                    let left = left.transform_data(constant_folder);
                    let right = right.transform_data(constant_folder);
                    Some(match (left.as_word(), right.as_word()) {
                        (Some(a), Some(b)) => SVD::new_known(a.lt(b)),
                        _ => SVD::LessThan { left, right },
                    })
                }
                SVD::GreaterThan { left, right } => {
                    let left = left.transform_data(constant_folder);
                    let right = right.transform_data(constant_folder);
                    Some(match (left.as_word(), right.as_word()) {
                        (Some(a), Some(b)) => SVD::new_known(a.gt(b)),
                        _ => SVD::LessThan { left, right },
                    })
                }
                SVD::SignedLessThan { left, right } => {
                    let left = left.transform_data(constant_folder);
                    let right = right.transform_data(constant_folder);
                    Some(match (left.as_word(), right.as_word()) {
                        (Some(a), Some(b)) => SVD::new_known(a.signed_lt(b)),
                        _ => SVD::LessThan { left, right },
                    })
                }
                SVD::SignedGreaterThan { left, right } => {
                    let left = left.transform_data(constant_folder);
                    let right = right.transform_data(constant_folder);
                    Some(match (left.as_word(), right.as_word()) {
                        (Some(a), Some(b)) => SVD::new_known(a.signed_gt(b)),
                        _ => SVD::LessThan { left, right },
                    })
                }
                SVD::Equals { left, right } => {
                    let left = left.transform_data(constant_folder);
                    let right = right.transform_data(constant_folder);
                    Some(match (left.as_word(), right.as_word()) {
                        (Some(a), Some(b)) => SVD::new_known(KnownWord::from(a == b)),
                        _ => SVD::LessThan { left, right },
                    })
                }
                SVD::IsZero { number } => {
                    let number = number.transform_data(constant_folder);
                    Some(match number.as_word() {
                        Some(a) => SVD::new_known(a.is_zero()),
                        _ => SVD::IsZero { number },
                    })
                }
                SVD::And { left, right } => {
                    let left = left.transform_data(constant_folder);
                    let right = right.transform_data(constant_folder);
                    Some(match (left.as_word(), right.as_word()) {
                        (Some(a), Some(b)) => SVD::new_known(a & b),
                        _ => SVD::And { left, right },
                    })
                }
                SVD::Or { left, right } => {
                    let left = left.transform_data(constant_folder);
                    let right = right.transform_data(constant_folder);
                    Some(match (left.as_word(), right.as_word()) {
                        (Some(a), Some(b)) => SVD::new_known(a | b),
                        _ => SVD::Or { left, right },
                    })
                }
                SVD::Xor { left, right } => {
                    let left = left.transform_data(constant_folder);
                    let right = right.transform_data(constant_folder);
                    Some(match (left.as_word(), right.as_word()) {
                        (Some(a), Some(b)) => SVD::new_known(a ^ b),
                        _ => SVD::Xor { left, right },
                    })
                }
                SVD::Not { value } => {
                    let value = value.transform_data(constant_folder);
                    Some(match value.as_word() {
                        Some(a) => SVD::new_known(!a),
                        _ => SVD::Not { value },
                    })
                }
                SVD::LeftShift { shift, value } => {
                    let shift = shift.transform_data(constant_folder);
                    let value = value.transform_data(constant_folder);
                    Some(match (shift.as_word(), value.as_word()) {
                        (Some(s), Some(v)) => SVD::new_known(v << s),
                        _ => SVD::LeftShift { shift, value },
                    })
                }
                SVD::RightShift { shift, value } => {
                    let shift = shift.transform_data(constant_folder);
                    let value = value.transform_data(constant_folder);
                    Some(match (shift.as_word(), value.as_word()) {
                        (Some(s), Some(v)) => SVD::new_known(v >> s),
                        _ => SVD::RightShift { shift, value },
                    })
                }
                SVD::ArithmeticRightShift { shift, value } => {
                    let shift = shift.transform_data(constant_folder);
                    let value = value.transform_data(constant_folder);
                    Some(match (shift.as_word(), value.as_word()) {
                        (Some(s), Some(v)) => SVD::new_known(v.sar(s)),
                        _ => SVD::RightShift { shift, value },
                    })
                }
                _ => None,
            }
        }

        self.clone().transform(constant_folder)
    }

    /// Gets the direct children of the provided node.
    #[allow(clippy::match_same_arms)]
    #[must_use]
    pub fn children(&self) -> Vec<BoxedVal<AuxData>> {
        match self {
            Self::Value { .. } => vec![],
            Self::KnownData { .. } => vec![],
            Self::Add { left, right } => vec![left, right],
            Self::Multiply { left, right } => vec![left, right],
            Self::Subtract { left, right } => vec![left, right],
            Self::Divide { dividend, divisor } => vec![dividend, divisor],
            Self::SignedDivide { dividend, divisor } => vec![dividend, divisor],
            Self::Modulo { dividend, divisor } => vec![dividend, divisor],
            Self::SignedModulo { dividend, divisor } => vec![dividend, divisor],
            Self::Exp { value, exponent } => vec![value, exponent],
            Self::SignExtend { size, value } => vec![size, value],
            Self::CallWithValue {
                gas,
                address,
                value,
                argument_data,
                ret_offset,
                ret_size,
            } => vec![gas, address, value, argument_data, ret_offset, ret_size],
            Self::CallWithoutValue {
                gas,
                address,
                argument_data,
                ret_offset,
                ret_size,
            } => vec![gas, address, argument_data, ret_offset, ret_size],
            Self::Sha3 { data } => vec![data],
            Self::Address => vec![],
            Self::Balance { address } => vec![address],
            Self::Origin => vec![],
            Self::Caller => vec![],
            Self::CallValue => vec![],
            Self::GasPrice => vec![],
            Self::ExtCodeHash { address } => vec![address],
            Self::BlockHash { block_number } => vec![block_number],
            Self::CoinBase => vec![],
            Self::BlockTimestamp => vec![],
            Self::BlockNumber => vec![],
            Self::Prevrandao => vec![],
            Self::GasLimit => vec![],
            Self::ChainId => vec![],
            Self::SelfBalance => vec![],
            Self::BaseFee => vec![],
            Self::Gas => vec![],
            Self::Log { data, topics } => {
                let mut vec = vec![data];
                vec.extend(topics);
                vec
            }
            Self::Create { value, data } => vec![value, data],
            Self::Create2 { value, data, salt } => vec![value, data, salt],
            Self::SelfDestruct { target } => vec![target],
            Self::LessThan { left, right } => vec![left, right],
            Self::GreaterThan { left, right } => vec![left, right],
            Self::SignedLessThan { left, right } => vec![left, right],
            Self::SignedGreaterThan { left, right } => vec![left, right],
            Self::Equals { left, right } => vec![left, right],
            Self::IsZero { number } => vec![number],
            Self::And { left, right } => vec![left, right],
            Self::Or { left, right } => vec![left, right],
            Self::Xor { left, right } => vec![left, right],
            Self::Not { value } => vec![value],
            Self::LeftShift { shift, value } => vec![shift, value],
            Self::RightShift { shift, value } => vec![shift, value],
            Self::ArithmeticRightShift { shift, value } => vec![shift, value],
            Self::CallData { offset, size, .. } => vec![offset, size],
            Self::CallDataSize => vec![],
            Self::CodeCopy { offset, size } => vec![offset, size],
            Self::ExtCodeSize { address } => vec![address],
            Self::ExtCodeCopy {
                address,
                offset,
                size,
            } => vec![address, offset, size],
            Self::ReturnData { offset, size } => vec![offset, size],
            Self::Return { data } => vec![data],
            Self::Revert { data } => vec![data],
            Self::UnwrittenStorageValue { key } => vec![key],
            Self::SLoad { key, value } => vec![key, value],
            Self::StorageSlot { key } => vec![key],
            Self::StorageWrite { key, value } => vec![key, value],
            Self::Concat { values } => values.iter().collect(),
            Self::MappingIndex { slot, key, .. } => vec![slot, key],
            Self::DynamicArrayIndex { slot, index } => vec![slot, index],
            Self::SubWord { value, .. } => vec![value],
            Self::Shifted { value, .. } => vec![value],
            Self::Packed { elements } => elements.iter().map(|e| &e.value).collect(),
        }
        .into_iter()
        .cloned()
        .collect()
    }
}

impl<AuxData> Display for SymbolicValueData<AuxData>
where
    AuxData: Debug,
{
    /// A pretty printer for symbolic values to aid in visual debugging.
    ///
    /// It omits the instruction pointer, provenance, and auxiliary data
    /// information. If you need these, instead use the [`Debug`] implementation
    /// for these values.
    ///
    /// # Conventions
    ///
    /// All value representations use `snake_case` representations so as to
    /// visually contrast with the type representations which use `PascalCase`
    /// naming.
    ///
    /// This format uses parentheses for grouping and function-call-like
    /// operations. It uses square brackets to denote access-style operations.
    #[allow(clippy::too_many_lines)] // No sense in splitting it up
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Value { id } => write!(f, "{}", clip_uuid(id)),
            Self::KnownData { value } => write!(f, "{value}"),
            Self::Add { left, right } => write!(f, "({left} + {right})"),
            Self::Multiply { left, right } => write!(f, "({left} * {right})"),
            Self::Subtract { left, right } => write!(f, "({left} - {right})"),
            Self::Divide { dividend, divisor } => write!(f, "({dividend} / {divisor})"),
            Self::SignedDivide { dividend, divisor } => write!(f, "({dividend} s/ {divisor})"),
            Self::Modulo { dividend, divisor } => write!(f, "({dividend} % {divisor})"),
            Self::SignedModulo { dividend, divisor } => write!(f, "({dividend} s% {divisor})"),
            Self::Exp { value, exponent } => write!(f, "({value} ** {exponent})"),
            Self::SignExtend { size, value } => write!(f, "sign_ext({size}, {value})"),
            Self::CallWithValue {
                gas,
                address,
                value,
                argument_data,
                ret_offset,
                ret_size,
            } => write!(
                f,
                "call_val({gas}, {address}, {value}, {argument_data}, {ret_offset}, {ret_size})"
            ),
            Self::CallWithoutValue {
                gas,
                address,
                argument_data,
                ret_offset,
                ret_size,
            } => write!(
                f,
                "call_val({gas}, {address}, {argument_data}, {ret_offset}, {ret_size})"
            ),
            Self::Sha3 { data } => write!(f, "sha3({data})"),
            Self::Address => write!(f, "address(this)"),
            Self::Balance { address } => write!(f, "balance({address})"),
            Self::Origin => write!(f, "tx.origin"),
            Self::Caller => write!(f, "msg.sender"),
            Self::CallValue => write!(f, "msg.value"),
            Self::GasPrice => write!(f, "tx.gasprice"),
            Self::ExtCodeHash { address } => write!(f, "ext_code_hash({address})"),
            Self::BlockHash { block_number } => write!(f, "block_hash({block_number}"),
            Self::CoinBase => write!(f, "block.coinbase"),
            Self::BlockTimestamp => write!(f, "block.timestamp"),
            Self::BlockNumber => write!(f, "block.number"),
            Self::Prevrandao => write!(f, "block.prevrandao"),
            Self::GasLimit => write!(f, "block.gaslimit"),
            Self::ChainId => write!(f, "block.chain_id"),
            Self::SelfBalance => write!(f, "address(this).balance"),
            Self::BaseFee => write!(f, "block.basefee"),
            Self::Gas => write!(f, "gasRemaining"),
            Self::Log { data, topics } => write!(f, "log({data}, {topics:?})"),
            Self::Create { value, data } => write!(f, "create({value}, {data})"),
            Self::Create2 { value, data, salt } => write!(f, "create({value}, {data}, {salt})"),
            Self::SelfDestruct { target } => write!(f, "self_destruct({target})"),
            Self::LessThan { left, right } => write!(f, "({left} < {right})"),
            Self::GreaterThan { left, right } => write!(f, "({left} > {right})"),
            Self::SignedLessThan { left, right } => write!(f, "({left} s< {right})"),
            Self::SignedGreaterThan { left, right } => write!(f, "({left} s> {right})"),
            Self::Equals { left, right } => write!(f, "({left} == {right})"),
            Self::IsZero { number } => write!(f, "({number} == 0)"),
            Self::And { left, right } => write!(f, "({left} & {right})"),
            Self::Or { left, right } => write!(f, "({left} | {right})"),
            Self::Xor { left, right } => write!(f, "({left} ^ {right})"),
            Self::Not { value } => write!(f, "!{value}"),
            Self::LeftShift { shift, value } => write!(f, "({value} << {shift})"),
            Self::RightShift { shift, value } => write!(f, "({value} >> {shift})"),
            Self::ArithmeticRightShift { shift, value } => write!(f, "({value} >>> {shift})"),
            Self::CallData { id, offset, size } => {
                write!(f, "call_data({})[{offset}, {size}]", clip_uuid(id))
            }
            Self::CallDataSize => write!(f, "call_data_size"),
            Self::CodeCopy { offset, size } => write!(f, "code_copy[{offset}, {size}]"),
            Self::ExtCodeSize { address } => write!(f, "ext_code_size({address})"),
            Self::ExtCodeCopy {
                address,
                offset,
                size,
            } => write!(f, "ext_code_copy({address})[{offset}, {size}]"),
            Self::ReturnData { offset, size } => {
                write!(f, "return_data_copy[{offset}, {size}]")
            }
            Self::Return { data } => write!(f, "return({data})"),
            Self::Revert { data } => write!(f, "revert({data})"),
            Self::UnwrittenStorageValue { key } => write!(f, "uninit_storage({key})"),
            Self::SLoad { key, value } => write!(f, "(s_load[{key}] => {value})"),
            Self::StorageSlot { key } => write!(f, "slot({key})"),
            Self::StorageWrite { key, value } => write!(f, "(s_store[{key}] = {value})"),
            Self::Concat { values } => {
                write!(f, "concat(")?;
                for (i, value) in values.iter().enumerate() {
                    write!(f, "{value}")?;
                    if i + 1 < values.len() {
                        write!(f, ", ")?;
                    }
                }
                write!(f, ")")
            }
            Self::MappingIndex {
                slot,
                key,
                projection,
            } => {
                write!(f, "mapping_ix({slot})[{}][{key}]", projection.unwrap_or(0))
            }
            Self::DynamicArrayIndex { slot, index } => write!(f, "dyn_arr_ix({slot})[{index}]"),
            Self::SubWord {
                value,
                offset,
                size,
            } => write!(f, "sub_word({value})[{offset}, {size}]"),
            Self::Shifted { offset, value } => write!(f, "shifted({value})[{offset}]"),
            Self::Packed { elements } => {
                write!(f, "packed(")?;
                for (i, value) in elements.iter().enumerate() {
                    write!(f, "{value}")?;
                    if i + 1 < elements.len() {
                        write!(f, ", ")?;
                    }
                }
                write!(f, ")")
            }
        }
    }
}

/// A representation of a span within a packed encoding.
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct PackedSpan<AuxData> {
    /// The position in the containing word at which the span begins.
    pub offset: usize,

    /// The size of the span within the containing word.
    pub size: usize,

    /// The value of the span.
    pub value: BoxedVal<AuxData>,
}

impl<AuxData> PackedSpan<AuxData> {
    /// Creates a span with a known `size` at `offset` represented by `value`.
    #[must_use]
    pub fn new(offset: usize, size: usize, value: BoxedVal<AuxData>) -> Self {
        Self {
            offset,
            size,
            value,
        }
    }
}

impl<AuxData> PackedSpan<AuxData>
where
    AuxData: Clone + PartialEq,
{
    /// Transforms the [`Self::value`] within the span with the provided
    /// `transform`.
    ///
    /// This has the same semantics as for [`SymbolicValueData::transform`].
    #[must_use]
    pub fn transform(
        &self,
        transform: impl Fn(&SVD<AuxData>) -> Option<SVD<AuxData>> + Copy,
    ) -> Self {
        let new_data = self.value.transform_data(transform);
        Self {
            offset: self.offset,
            size:   self.size,
            value:  new_data,
        }
    }
}

impl<AuxData> Display for PackedSpan<AuxData>
where
    AuxData: Debug,
{
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "span({})[{}, {}]", self.value, self.offset, self.size)
    }
}

/// A descriptor for where a symbolic value originated in the program.
///
/// In essence, these can be thought of as tags that provide more information
/// about the given value when the value is newly and dynamically created.
#[derive(Copy, Clone, Debug, Eq, Hash, Derivative, PartialEq)]
pub enum Provenance {
    /// The value originated from the result of the `CALLDATASIZE` opcode.
    CallDataSize,

    /// The value deposited by the caller.
    CallValue,

    /// The size of the memory at the time the program requested it.
    MSize,

    /// The value originated from the result of the `GAS` opcode.
    Gas,

    /// The value originated in operations performed by the bytecode in the
    /// course of execution.
    Execution,

    /// The value was encoded in the bytecode of the program.
    Bytecode,

    /// The value originated from a read of the current program counter.
    ProgramCounter,

    /// The value originated from the `RETURNDATASIZE` opcode.
    ReturnDataSize,

    /// The value originated from the `SELFBALANCE` opcode.
    SelfBalance,

    /// The value was constructed by the analysis in order to aid the process.
    Synthetic,

    /// The value originated from an `MLOAD` from uninitialized memory.
    UninitializedMemory,

    /// The value originated from an `SLOAD` from uninitialized storage.
    NonWrittenStorage,

    /// The data came from the data returned from a message call.
    MessageCall,

    /// There is no concrete source for this variable.
    Unknown,
}

/// A value payload that is constructed when consuming a symbolic value.
#[derive(Clone, Debug, Eq, Derivative)]
#[derivative(
    Hash(bound = "AuxData: std::hash::Hash"),
    PartialEq(bound = "AuxData: std::cmp::PartialEq")
)]
pub struct ValuePayload<AuxData> {
    /// The data payload of the symbolic value.
    pub data: SymbolicValueData<AuxData>,

    /// The auxiliary data payload of the symbolic value.
    pub aux: AuxData,
}

impl<AuxData> ValuePayload<AuxData>
where
    AuxData: Clone + PartialEq,
{
    /// Constructs a new value payload wrapping `data` and `aux`.
    #[must_use]
    pub fn new(data: SymbolicValueData<AuxData>, aux: AuxData) -> Self {
        Self { data, aux }
    }
}

#[cfg(test)]
mod test {
    use ethnum::U256;
    use uuid::Uuid;

    use crate::vm::value::{known::KnownWord, Provenance, RSV, RSVD};

    #[test]
    fn equality_ignores_instruction_pointer() {
        let id = Uuid::new_v4();
        let data = RSVD::Value { id };
        let value_1 = RSV::new_synthetic(0, data.clone());
        let value_2 = RSV::new_synthetic(0, data);

        assert_eq!(value_1, value_2);
    }

    #[test]
    fn equality_ignores_provenance() {
        let id = Uuid::new_v4();
        let data = RSVD::Value { id };
        let value_1 = RSV::new_from_execution(0, data.clone(), None);
        let value_2 = RSV::new_synthetic(1, data);

        assert_eq!(value_1, value_2);
    }

    #[test]
    fn strict_equality_includes_instruction_pointer() {
        let id = Uuid::new_v4();
        let data = RSVD::Value { id };
        let value_1 = RSV::new_synthetic(0, data.clone());
        let value_2 = RSV::new_synthetic(1, data);

        assert!(!value_1.strict_eq(&value_2));
    }

    #[test]
    fn strict_equality_ignores_provenance() {
        let id = Uuid::new_v4();
        let data = RSVD::Value { id };
        let value_1 = RSV::new_from_execution(0, data.clone(), None);
        let value_2 = RSV::new_synthetic(1, data);

        assert!(!value_1.strict_eq(&value_2));
    }

    #[test]
    fn constant_folds_add() {
        let left = RSV::new_known_value(0, KnownWord::from(1), Provenance::Synthetic, None);
        let right = RSV::new_known_value(1, KnownWord::from(7), Provenance::Synthetic, None);
        let add = RSV::new(2, RSVD::Add { left, right }, Provenance::Synthetic, None);

        let folded = add.constant_fold();

        assert_eq!(
            folded,
            RSV::new_known_value(2, KnownWord::from(8), Provenance::Synthetic, None)
        );
    }

    #[test]
    fn constant_folds_mul() {
        let left = RSV::new_known_value(0, KnownWord::from(1), Provenance::Synthetic, None);
        let right = RSV::new_known_value(1, KnownWord::from(7), Provenance::Synthetic, None);
        let add = RSV::new(
            2,
            RSVD::Multiply { left, right },
            Provenance::Synthetic,
            None,
        );

        let folded = add.constant_fold();

        assert_eq!(
            folded,
            RSV::new_known_value(2, KnownWord::from(7), Provenance::Synthetic, None)
        );
    }

    #[test]
    fn constant_folds_sub() {
        let left = RSV::new_known_value(0, KnownWord::from(7), Provenance::Synthetic, None);
        let right = RSV::new_known_value(1, KnownWord::from(1), Provenance::Synthetic, None);
        let add = RSV::new(
            2,
            RSVD::Subtract { left, right },
            Provenance::Synthetic,
            None,
        );

        let folded = add.constant_fold();

        assert_eq!(
            folded,
            RSV::new_known_value(2, KnownWord::from(6), Provenance::Synthetic, None)
        );
    }

    #[test]
    fn constant_folds_div() {
        let dividend = RSV::new_known_value(0, KnownWord::from(7), Provenance::Synthetic, None);
        let divisor = RSV::new_known_value(1, KnownWord::from(1), Provenance::Synthetic, None);
        let add = RSV::new(
            2,
            RSVD::Divide { dividend, divisor },
            Provenance::Synthetic,
            None,
        );

        let folded = add.constant_fold();

        assert_eq!(
            folded,
            RSV::new_known_value(2, KnownWord::from(7), Provenance::Synthetic, None)
        );
    }

    #[test]
    fn constant_folds_signed_div() {
        let dividend = RSV::new_known_value(0, KnownWord::from(7), Provenance::Synthetic, None);
        let divisor = RSV::new_known_value(1, KnownWord::from(1), Provenance::Synthetic, None);
        let add = RSV::new(
            2,
            RSVD::SignedDivide { dividend, divisor },
            Provenance::Synthetic,
            None,
        );

        let folded = add.constant_fold();

        assert_eq!(
            folded,
            RSV::new_known_value(2, KnownWord::from(7), Provenance::Synthetic, None)
        );
    }

    #[test]
    fn constant_folds_mod() {
        let dividend = RSV::new_known_value(0, KnownWord::from(7), Provenance::Synthetic, None);
        let divisor = RSV::new_known_value(1, KnownWord::from(2), Provenance::Synthetic, None);
        let add = RSV::new(
            2,
            RSVD::Modulo { dividend, divisor },
            Provenance::Synthetic,
            None,
        );

        let folded = add.constant_fold();

        assert_eq!(
            folded,
            RSV::new_known_value(2, KnownWord::from(1), Provenance::Synthetic, None)
        );
    }

    #[test]
    fn constant_folds_signed_mod() {
        let dividend = RSV::new_known_value(0, KnownWord::from(7), Provenance::Synthetic, None);
        let divisor = RSV::new_known_value(1, KnownWord::from(2), Provenance::Synthetic, None);
        let add = RSV::new(
            2,
            RSVD::SignedModulo { dividend, divisor },
            Provenance::Synthetic,
            None,
        );

        let folded = add.constant_fold();

        assert_eq!(
            folded,
            RSV::new_known_value(2, KnownWord::from(1), Provenance::Synthetic, None)
        );
    }

    #[test]
    fn constant_folds_exp() {
        let value = RSV::new_known_value(0, KnownWord::from(7), Provenance::Synthetic, None);
        let exponent = RSV::new_known_value(1, KnownWord::from(2), Provenance::Synthetic, None);
        let add = RSV::new(
            2,
            RSVD::Exp { value, exponent },
            Provenance::Synthetic,
            None,
        );

        let folded = add.constant_fold();

        assert_eq!(
            folded,
            RSV::new_known_value(2, KnownWord::from(49), Provenance::Synthetic, None)
        );
    }

    #[test]
    fn constant_folds_less_than() {
        let left = RSV::new_known_value(0, KnownWord::from(1), Provenance::Synthetic, None);
        let right = RSV::new_known_value(1, KnownWord::from(7), Provenance::Synthetic, None);
        let add = RSV::new(
            2,
            RSVD::LessThan { left, right },
            Provenance::Synthetic,
            None,
        );

        let folded = add.constant_fold();

        assert_eq!(
            folded,
            RSV::new_known_value(2, KnownWord::from(true), Provenance::Synthetic, None)
        );
    }

    #[test]
    fn constant_folds_greater_than() {
        let left = RSV::new_known_value(0, KnownWord::from(1), Provenance::Synthetic, None);
        let right = RSV::new_known_value(1, KnownWord::from(7), Provenance::Synthetic, None);
        let add = RSV::new(
            2,
            RSVD::GreaterThan { left, right },
            Provenance::Synthetic,
            None,
        );

        let folded = add.constant_fold();

        assert_eq!(
            folded,
            RSV::new_known_value(2, KnownWord::from(false), Provenance::Synthetic, None)
        );
    }

    #[test]
    fn constant_folds_signed_less_than() {
        let left = RSV::new_known_value(0, KnownWord::from(1), Provenance::Synthetic, None);
        let right = RSV::new_known_value(1, KnownWord::from(7), Provenance::Synthetic, None);
        let add = RSV::new(
            2,
            RSVD::SignedLessThan { left, right },
            Provenance::Synthetic,
            None,
        );

        let folded = add.constant_fold();

        assert_eq!(
            folded,
            RSV::new_known_value(2, KnownWord::from(true), Provenance::Synthetic, None)
        );
    }

    #[test]
    fn constant_folds_signed_greater_than() {
        let left = RSV::new_known_value(0, KnownWord::from(1), Provenance::Synthetic, None);
        let right = RSV::new_known_value(1, KnownWord::from(7), Provenance::Synthetic, None);
        let add = RSV::new(
            2,
            RSVD::SignedGreaterThan { left, right },
            Provenance::Synthetic,
            None,
        );

        let folded = add.constant_fold();

        assert_eq!(
            folded,
            RSV::new_known_value(2, KnownWord::from(false), Provenance::Synthetic, None)
        );
    }

    #[test]
    fn constant_folds_equals() {
        let left = RSV::new_known_value(0, KnownWord::from(1), Provenance::Synthetic, None);
        let right = RSV::new_known_value(1, KnownWord::from(7), Provenance::Synthetic, None);
        let add = RSV::new(2, RSVD::Equals { left, right }, Provenance::Synthetic, None);

        let folded = add.constant_fold();

        assert_eq!(
            folded,
            RSV::new_known_value(2, KnownWord::from(false), Provenance::Synthetic, None)
        );
    }

    #[test]
    fn constant_folds_is_zero() {
        let number = RSV::new_known_value(1, KnownWord::from(7), Provenance::Synthetic, None);
        let add = RSV::new(2, RSVD::IsZero { number }, Provenance::Synthetic, None);

        let folded = add.constant_fold();

        assert_eq!(
            folded,
            RSV::new_known_value(2, KnownWord::from(false), Provenance::Synthetic, None)
        );
    }

    #[test]
    fn constant_folds_and() {
        let left = RSV::new_known_value(0, KnownWord::from(0b0101), Provenance::Synthetic, None);
        let right = RSV::new_known_value(1, KnownWord::from(0b1110), Provenance::Synthetic, None);
        let add = RSV::new(2, RSVD::And { left, right }, Provenance::Synthetic, None);

        let folded = add.constant_fold();

        assert_eq!(
            folded,
            RSV::new_known_value(2, KnownWord::from(0b0100), Provenance::Synthetic, None)
        );
    }

    #[test]
    fn constant_folds_or() {
        let left = RSV::new_known_value(0, KnownWord::from(0b0101), Provenance::Synthetic, None);
        let right = RSV::new_known_value(1, KnownWord::from(0b1110), Provenance::Synthetic, None);
        let add = RSV::new(2, RSVD::Or { left, right }, Provenance::Synthetic, None);

        let folded = add.constant_fold();

        assert_eq!(
            folded,
            RSV::new_known_value(2, KnownWord::from(0b1111), Provenance::Synthetic, None)
        );
    }

    #[test]
    fn constant_folds_xor() {
        let left = RSV::new_known_value(0, KnownWord::from(0b0101), Provenance::Synthetic, None);
        let right = RSV::new_known_value(1, KnownWord::from(0b1110), Provenance::Synthetic, None);
        let add = RSV::new(2, RSVD::Xor { left, right }, Provenance::Synthetic, None);

        let folded = add.constant_fold();

        assert_eq!(
            folded,
            RSV::new_known_value(2, KnownWord::from(0b1011), Provenance::Synthetic, None)
        );
    }

    #[test]
    fn constant_folds_not() {
        let value = RSV::new_known_value(1, KnownWord::from(1), Provenance::Synthetic, None);
        let add = RSV::new(2, RSVD::Not { value }, Provenance::Synthetic, None);

        let folded = add.constant_fold();

        assert_eq!(
            folded,
            RSV::new_known_value(
                2,
                KnownWord::from_le(!U256::from(1u8)),
                Provenance::Synthetic,
                None
            )
        );
    }

    #[test]
    fn constant_folds_left_shift() {
        let shift = RSV::new_known_value(0, KnownWord::from(2), Provenance::Synthetic, None);
        let value = RSV::new_known_value(1, KnownWord::from(0b1110), Provenance::Synthetic, None);
        let add = RSV::new(
            2,
            RSVD::LeftShift { shift, value },
            Provenance::Synthetic,
            None,
        );

        let folded = add.constant_fold();

        assert_eq!(
            folded,
            RSV::new_known_value(2, KnownWord::from(0b11_1000), Provenance::Synthetic, None)
        );
    }

    #[test]
    fn constant_folds_right_shift() {
        let shift = RSV::new_known_value(0, KnownWord::from(2), Provenance::Synthetic, None);
        let value = RSV::new_known_value(1, KnownWord::from(0b1110), Provenance::Synthetic, None);
        let add = RSV::new(
            2,
            RSVD::RightShift { shift, value },
            Provenance::Synthetic,
            None,
        );

        let folded = add.constant_fold();

        assert_eq!(
            folded,
            RSV::new_known_value(2, KnownWord::from_le(0b11u32), Provenance::Synthetic, None)
        );
    }

    #[test]
    fn constant_folds_arithmetic_right_shift() {
        let shift = RSV::new_known_value(0, KnownWord::from(2), Provenance::Synthetic, None);
        let value = RSV::new_known_value(1, KnownWord::from(0b1110), Provenance::Synthetic, None);
        let add = RSV::new(
            2,
            RSVD::ArithmeticRightShift { shift, value },
            Provenance::Synthetic,
            None,
        );

        let folded = add.constant_fold();

        assert_eq!(
            folded,
            RSV::new_known_value(2, KnownWord::from(0b11), Provenance::Synthetic, None)
        );
    }

    #[test]
    fn constant_folds_deeply() {
        let inner_add_left =
            RSV::new_known_value(0, KnownWord::from(1), Provenance::Synthetic, None);
        let inner_add_right =
            RSV::new_known_value(1, KnownWord::from(7), Provenance::Synthetic, None);
        let inner_add = RSV::new(
            2,
            RSVD::Add {
                left:  inner_add_left,
                right: inner_add_right,
            },
            Provenance::Synthetic,
            None,
        );
        let outer_add_left =
            RSV::new_known_value(3, KnownWord::from(8), Provenance::Synthetic, None);
        let outer_add = RSV::new(
            2,
            RSVD::Add {
                left:  outer_add_left,
                right: inner_add,
            },
            Provenance::Synthetic,
            None,
        );

        let folded = outer_add.constant_fold();

        assert_eq!(
            folded,
            RSV::new_known_value(2, KnownWord::from(16), Provenance::Synthetic, None)
        );
    }

    #[test]
    fn can_compute_size() {
        // Construct some data
        let value_1 = RSV::new_value(0, Provenance::Synthetic);
        let value_2 = RSV::new_value(1, Provenance::Synthetic);
        let value_3 = RSV::new_value(2, Provenance::Synthetic);
        let add = RSV::new_synthetic(
            3,
            RSVD::Add {
                left:  value_1.clone(),
                right: value_2.clone(),
            },
        );
        let mul = RSV::new_synthetic(
            4,
            RSVD::Multiply {
                left:  add.clone(),
                right: value_3.clone(),
            },
        );

        // Check they all have the right size
        assert_eq!(value_1.size(), 1);
        assert_eq!(value_2.size(), 1);
        assert_eq!(value_3.size(), 1);
        assert_eq!(add.size(), 3);
        assert_eq!(mul.size(), 5);
    }

    #[test]
    fn computes_size_correctly_when_transformed() {
        // Construct some data
        let value_1 = RSV::new_value(0, Provenance::Synthetic);
        let value_2 = RSV::new_value(1, Provenance::Synthetic);
        let value_3 = RSV::new_value(2, Provenance::Synthetic);
        let add = RSV::new_synthetic(
            3,
            RSVD::Add {
                left:  value_1.clone(),
                right: value_2.clone(),
            },
        );
        let mul = RSV::new_synthetic(
            4,
            RSVD::Multiply {
                left:  add.clone(),
                right: value_3.clone(),
            },
        );

        // Check they all have the right size
        assert_eq!(value_1.size(), 1);
        assert_eq!(value_2.size(), 1);
        assert_eq!(value_3.size(), 1);
        assert_eq!(add.size(), 3);
        assert_eq!(mul.size(), 5);

        // Transform it, which will implicitly change the depth
        let transformed = mul.transform_data(|data| match data {
            RSVD::Add { .. } => Some(RSVD::new_value()),
            _ => None,
        });

        // Check that the size is correct
        assert_eq!(transformed.size, 3);
    }

    #[test]
    fn culls_value_trees_above_limit() {
        let value_1 = RSV::new_value(0, Provenance::Synthetic);
        let value_2 = RSV::new_value(1, Provenance::Synthetic);
        let value_3 = RSV::new_value(2, Provenance::Synthetic);
        let add = RSV::new_synthetic(
            3,
            RSVD::Add {
                left:  value_1.clone(),
                right: value_2.clone(),
            },
        );
        let mul = RSV::new_synthetic(
            4,
            RSVD::Multiply {
                left:  add.clone(),
                right: value_3.clone(),
            },
        );

        // Check they all have the right size
        assert_eq!(value_1.size(), 1);
        assert_eq!(value_2.size(), 1);
        assert_eq!(value_3.size(), 1);
        assert_eq!(add.size(), 3);
        assert_eq!(mul.size(), 5);

        // Wrap in a node with a very low limit
        let not = RSV::new(5, RSVD::Not { value: mul }, Provenance::Synthetic, Some(2));

        // Check the payload is just a bare value
        assert!(matches!(not.data, RSVD::Value { .. }));
    }
}
