library IssuanceValidationUtils {
    using SafeMath for uint256;
    using SafeCast for int256;
    using PreciseUnitMath for uint256;

    /**
     * Validates component transfer IN to SetToken during issuance/redemption. Reverts if Set is undercollateralized post transfer.
     * NOTE: Call this function immediately after transfer IN but before calling external hooks (if any).
     *
     * @param _setToken             Instance of the SetToken being issued/redeemed
     * @param _component            Address of component being transferred in/out
     * @param _initialSetSupply     Initial SetToken supply before issuance/redemption
     * @param _componentQuantity    Amount of component transferred into SetToken
     */
    function validateCollateralizationPostTransferInPreHook(
        ISetToken _setToken, 
        address _component, 
        uint256 _initialSetSupply,
        uint256 _componentQuantity
    )
        internal
        view
    {
        uint256 newComponentBalance = IERC20(_component).balanceOf(address(_setToken));

        uint256 defaultPositionUnit = _setToken.getDefaultPositionRealUnit(address(_component)).toUint256();
        
        require(
            // Use preciseMulCeil to increase the lower bound and maintain over-collateralization
            newComponentBalance >= _initialSetSupply.preciseMulCeil(defaultPositionUnit).add(_componentQuantity),
            "Invalid transfer in. Results in undercollateralization"
        );
    }

    /**
     * Validates component transfer OUT of SetToken during issuance/redemption. Reverts if Set is undercollateralized post transfer.
     *
     * @param _setToken         Instance of the SetToken being issued/redeemed
     * @param _component        Address of component being transferred in/out
     * @param _finalSetSupply   Final SetToken supply after issuance/redemption
     */
    function validateCollateralizationPostTransferOut(
        ISetToken _setToken, 
        address _component, 
        uint256 _finalSetSupply
    )
        internal 
        view 
    {
        uint256 newComponentBalance = IERC20(_component).balanceOf(address(_setToken));

        uint256 defaultPositionUnit = _setToken.getDefaultPositionRealUnit(address(_component)).toUint256();

        require(
            // Use preciseMulCeil to increase lower bound and maintain over-collateralization
            newComponentBalance >= _finalSetSupply.preciseMulCeil(defaultPositionUnit),
            "Invalid transfer out. Results in undercollateralization"
        );
    }
}
/**
 * @title IssuanceValidationUtils
 * @author Set Protocol
 *
 * A collection of utility functions to help during issuance/redemption of SetToken.
 */
function validateCollateralizationPostTransferOut(
    ISetToken _setToken, 
    address _component, 
    uint256 _finalSetSupply
)
    internal 
    view 
{
    uint256 newComponentBalance = IERC20(_component).balanceOf(address(_setToken));

    uint256 defaultPositionUnit = _setToken.getDefaultPositionRealUnit(address(_component)).toUint256();

    require(
        // Use preciseMulCeil to increase lower bound and maintain over-collateralization
        newComponentBalance >= _finalSetSupply.preciseMulCeil(defaultPositionUnit),
        "Invalid transfer out. Results in undercollateralization"
    );
}

/**
 * Validates component transfer OUT of SetToken during issuance/redemption. Reverts if Set is undercollateralized post transfer.
 *
 * @param _setToken         Instance of the SetToken being issued/redeemed
 * @param _component        Address of component being transferred in/out
 * @param _finalSetSupply   Final SetToken supply after issuance/redemption
 */