// File: @openzeppelin/contracts/utils/introspection/ERC165.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol)

pragma solidity ^0.8.0;

import "./IERC165.sol";

/**
 * @dev Implementation of the {IERC165} interface.
 *
 * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
 * for the additional interface id that will be supported. For example:
 *
 * ```solidity
 * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
 *     return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
 * }
 * ```
 *
 * Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation.
 */
abstract contract ERC165 is IERC165 {
    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
        return interfaceId == type(IERC165).interfaceId;
    }
}

// File: @openzeppelin/contracts/utils/introspection/IERC165.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC165 standard, as defined in the
 * https://eips.ethereum.org/EIPS/eip-165[EIP].
 *
 * Implementers can declare support of contract interfaces, which can then be
 * queried by others ({ERC165Checker}).
 *
 * For an implementation, see {ERC165}.
 */
interface IERC165 {
    /**
     * @dev Returns true if this contract implements the interface defined by
     * `interfaceId`. See the corresponding
     * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
     * to learn more about how these ids are created.
     *
     * This function call must use less than 30 000 gas.
     */
    function supportsInterface(bytes4 interfaceId) external view returns (bool);
}

// File: @openzeppelin/contracts/utils/math/SafeMath.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (utils/math/SafeMath.sol)

pragma solidity ^0.8.0;

// CAUTION
// This version of SafeMath should only be used with Solidity 0.8 or later,
// because it relies on the compiler's built in overflow checks.

/**
 * @dev Wrappers over Solidity's arithmetic operations.
 *
 * NOTE: `SafeMath` is generally not needed starting with Solidity 0.8, since the compiler
 * now has built in overflow checking.
 */
library SafeMath {
    /**
     * @dev Returns the addition of two unsigned integers, with an overflow flag.
     *
     * _Available since v3.4._
     */
    function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            uint256 c = a + b;
            if (c < a) return (false, 0);
            return (true, c);
        }
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, with an overflow flag.
     *
     * _Available since v3.4._
     */
    function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b > a) return (false, 0);
            return (true, a - b);
        }
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, with an overflow flag.
     *
     * _Available since v3.4._
     */
    function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
            // benefit is lost if 'b' is also tested.
            // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
            if (a == 0) return (true, 0);
            uint256 c = a * b;
            if (c / a != b) return (false, 0);
            return (true, c);
        }
    }

    /**
     * @dev Returns the division of two unsigned integers, with a division by zero flag.
     *
     * _Available since v3.4._
     */
    function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b == 0) return (false, 0);
            return (true, a / b);
        }
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
     *
     * _Available since v3.4._
     */
    function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b == 0) return (false, 0);
            return (true, a % b);
        }
    }

    /**
     * @dev Returns the addition of two unsigned integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `+` operator.
     *
     * Requirements:
     *
     * - Addition cannot overflow.
     */
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        return a + b;
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting on
     * overflow (when the result is negative).
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     *
     * - Subtraction cannot overflow.
     */
    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
        return a - b;
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `*` operator.
     *
     * Requirements:
     *
     * - Multiplication cannot overflow.
     */
    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
        return a * b;
    }

    /**
     * @dev Returns the integer division of two unsigned integers, reverting on
     * division by zero. The result is rounded towards zero.
     *
     * Counterpart to Solidity's `/` operator.
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function div(uint256 a, uint256 b) internal pure returns (uint256) {
        return a / b;
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * reverting when dividing by zero.
     *
     * Counterpart to Solidity's `%` operator. This function uses a `revert`
     * opcode (which leaves remaining gas untouched) while Solidity uses an
     * invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function mod(uint256 a, uint256 b) internal pure returns (uint256) {
        return a % b;
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
     * overflow (when the result is negative).
     *
     * CAUTION: This function is deprecated because it requires allocating memory for the error
     * message unnecessarily. For custom revert reasons use {trySub}.
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     *
     * - Subtraction cannot overflow.
     */
    function sub(
        uint256 a,
        uint256 b,
        string memory errorMessage
    ) internal pure returns (uint256) {
        unchecked {
            require(b <= a, errorMessage);
            return a - b;
        }
    }

    /**
     * @dev Returns the integer division of two unsigned integers, reverting with custom message on
     * division by zero. The result is rounded towards zero.
     *
     * Counterpart to Solidity's `/` operator. Note: this function uses a
     * `revert` opcode (which leaves remaining gas untouched) while Solidity
     * uses an invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function div(
        uint256 a,
        uint256 b,
        string memory errorMessage
    ) internal pure returns (uint256) {
        unchecked {
            require(b > 0, errorMessage);
            return a / b;
        }
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * reverting with custom message when dividing by zero.
     *
     * CAUTION: This function is deprecated because it requires allocating memory for the error
     * message unnecessarily. For custom revert reasons use {tryMod}.
     *
     * Counterpart to Solidity's `%` operator. This function uses a `revert`
     * opcode (which leaves remaining gas untouched) while Solidity uses an
     * invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function mod(
        uint256 a,
        uint256 b,
        string memory errorMessage
    ) internal pure returns (uint256) {
        unchecked {
            require(b > 0, errorMessage);
            return a % b;
        }
    }
}

// File: contracts/product/interfaces/IERC20Adapter.sol



pragma solidity 0.8.17;

/**
 * @dev IERC20Adapter is an interface allowing to (mostly) comply with the ERC20 standard while remaining compliant with 
 * the financial regulations(mostly KYC and due dilligence obligations).
 * Each call to transfer()/transferFrom() or approve() has to be reviewed by *some* validator authority before either proceeding (by calling validateTransfer()
 * /validateApprove()) or cancelling (by calling rejectTransfer()/rejectApprove())
 * see TransferAdapterLibrary and ApproveAdapteLibrary for implementation.
 */
interface IERC20Adapter {
    /**
     * @dev Initiate a transfer request. Same semantic as ERC20's transfer function although the transfer
     * will only actually occur once reviewed by validator(who will call validateTransfer)
     */
    function transfer(address _to, uint256 _value) external returns (bool);

    /**
     * @dev Actually performs the transfer request corresponding to the given `transferHash`
     * Called by the validator authority
     */
    function validateTransfer(bytes32 transferHash) external returns (bool);

    /**
     * @dev Rejects(and thus, actually cancels) the transfer request corresponding to the given `transferHash`
     * Called by the validator authority
     */
    function rejectTransfer(bytes32 transferHash) external returns (bool);

    /**
     * @dev Initiate an approve request. Same semantic as ERC20's approve function although the approve
     * will only actually occur once reviewed by validator(who will call validateApprove)
     */
    function approve(address _spender, uint256 _value) external returns (bool);

    /**
     * @dev Same semantic as ERC20's allowance function
     */
    function allowance(address owner, address spender)
        external
        view
        returns (uint256);

    /**
     * @dev Initiate a transferFrom request. Same semantic as ERC20's transferFrom function although the transferFrom
     * will only actually occur once reviewed by validator(who will call validateTransfer)
     */
    function transferFrom(
        address from,
        address to,
        uint256 amount
    ) external returns (bool);

    /**
     * @dev Actually performs the approve request corresponding to the given `transferHash`
     * Called by the validator authority
     */
    function validateApprove(bytes32 approveHash) external returns (bool);

    /**
     * @dev Rejects(and thus, actually cancels) the approve request corresponding to the given `transferHash`
     * Called by the validator authority
     */
    function rejectApprove(bytes32 approveHash) external returns (bool);

    /**
     * @dev Emitted when a transfer actuelly occurs(that is, when validateTransfer() is called by the validator authority
     */
    event Transfer(address indexed _from, address indexed _to, uint256 _value);
    /**
     * @dev Emitted when an approve request is initiated
     */
    event ApproveRequest(
        bytes32 approveHash,
        address indexed owner,
        address indexed spender,
        uint256 value
    );
    /**
     * @dev Emitted when an approve request is rejected
     */
    event ApproveRejected(bytes32 approveHash);
    /**
     * @dev Emitted when an approve request is validated
     */
    event ApproveValidated(bytes32 approveHash);
    /**
     * @dev Emitted when an approve actually occurs(that is, when validateApprove() is called by the validator authority
     */
    event Approval(
        address indexed owner,
        address indexed spender,
        uint256 value
    );
    /**
     * @dev Emitted when a transfer request is initiated
     */
    event TransferRequest(
        bytes32 transferHash,
        address indexed from,
        address indexed to,
        uint256 value
    );
    /**
     * @dev Emitted when a transfer request is rejected
     */
    event TransferRejected(bytes32 transferHash);
    /**
     * @dev Emitted when a transfer request is validated
     */
    event TransferValidated(bytes32 transferHash);
}

// File: contracts/product/interfaces/IProduct.sol



pragma solidity 0.8.17;

/**
 * @dev This interface mostly lists all structures used by the Product contract
 */

// Maybe add common functions for products
interface IProduct {
    /**
     * @dev Details of a legal entity
     */
    struct LegalEntity {
        /**
         * @dev Legal Entity Identifier of the legal entity
        */
        string lei;
        /**
         * @dev Account address of the legal entity
         */
        address account;
    }

    /**
     * @dev Describes an issuance tranche
    */
    struct Tranche {
        uint256 nominalAmountCent;
        uint256 issueDate;
    }

    /**
     * @dev Represents a simple balance with value `balance` for address `account`
     */
    struct InitBalance {
        address account;
        uint256 balance;
    }
    // pourquoi pas dans ISettlement ?
    event TransferExecuted(uint256 settlementTransactionId);

    /**
     * @dev This structure describes the parameters of the product to create/issue
     * Most parameters are financial characteristics of the product
     */
    struct BasicProductInput {
        /**
         * @dev The balances to setup at the contract's creation
         * Used when migrating from an existing contract with existing balances
         */
        InitBalance[] initialBalances;
        /**
         * @dev The details of the initial issuance tranches
         */
        Tranche[] initialTranches;
        /**
         * @dev The address of a previous contract that is replaced with this one (allows to keep track of successive contract migrations)
         */
        address previousContract;
        /**
         * @dev FIGI code of the product(optional)
         */
        string figi;
        /**
         * @dev JSON string of extended information about the product. Can be roughly anything.
         * Will be used to hold financial information specific to a some products.
         */
        string extendedDataJson;
        /**
         * @dev String holding ESG information about the product (JSON format)
         */
        string esg;
        /**
         * @dev This product's note status
         */
        string noteStatus;
        /**
         * @dev This product's form of the node (e.g. Registered)
         */
        string formOfNote;
        /**
         * @dev Name of the issuance series (a group of tranches)
         */
        string series;
        /**
         * @dev The issuer's issuance program this product is part of
         */
        string program;
        /**
         * @dev The URL of the Settlement Transaction Repository that gives access to confidential information in the Settlement Transactions
         */
        string strUrl;
        /**
         * @dev The type of the financial product
         */
        string productType;
        /**
         * @dev Details of the product's guarantor legal entity
         */
        LegalEntity guarantor;
        /**
         * @dev Details of the product's issuer legal entity
         */
        LegalEntity issuer;
        /**
         * @dev Details of the product's paying agent legal entity
         */
        LegalEntity payingAgent;
        /**
         * @dev Details of the product's settlement agent legal entity
         */
        LegalEntity settlementAgent;
        /**
         * @dev Details of the product's registrar legal entity
         */
        LegalEntity registrar;
        /**
         * @dev The product's currency
         */
        string currency;
        /**
         * @dev The address of the IProductRegistry where the product should be listed
         */
        address registryAddress;
        /**
         * @dev String describing the market places where the product is listed
         */
        string listing;
        /**
         * @dev The governing law of the product
         */
        string governingLaw;
        /**
         * @dev The product's maturity date
         */
        uint256 maturityDate;
        /**
         * @dev The product's denomination in cents
         */
        uint256 denominationCent;
        /**
         * @dev The product's symbol
         */
        string symbol;
        /**
         * @dev The product's name
         */
        string name;
        /**
         * @dev The product's ISIN code
         */
        string isinCode;
    }
}

// File: contracts/product/interfaces/ISettlement.sol



pragma solidity 0.8.17;

import "../libraries/SettlementRepositoryLibrary.sol";

/**
 * @dev This interface lists the methods allowing to initiate and settle so-called Settlement Transaction.
 * Please refer to CAST framework's whitepaper for details about what Settlement Transactions are
 */
interface ISettlement {
    /**
     * @dev Initiate a Subscription Settlement Transaction (counterparty buys product on primary market from the product's issuer)
     * The tokens are locked waiting for the cash leg to settle (see functions confirmPaymentReceived/confirmPaymentTransferred)
     */
    function initiateSubscription(
        SettlementRepositoryLibrary.PartialSettlementTransaction
            calldata partialSettlementTransaction
    ) external;

    /**
     * @dev Initiate a Trade Settlement Transaction (one counterparty buys, the other sells)
     * The tokens are locked waiting for the cash leg to settle (see functions confirmPaymentReceived/confirmPaymentTransferred)
     */
    function initiateTrade(
        SettlementRepositoryLibrary.PartialSettlementTransaction
            calldata partialSettlementTransaction
    ) external;

    /**
     * @dev Initiate a Redemption Settlement Transactions (issuer redeems product to all holders and gets the tokens back)
     * The tokens are locked waiting for the cash leg to settle (see functions confirmPaymentReceived/confirmPaymentTransferred)
     */
    function initiateRedemption(
        SettlementRepositoryLibrary.PartialSettlementTransaction[]
            calldata settlementTransaction,
        string calldata extendedData
    ) external;

    /**
     * @dev Execute unilateral Transfer Settlement Transactions 
     * As there is no cash leg to settle, the tokens are transfered right away and the Settlement Transaction is marked as Settled
     */
    function executeTransfer(
        SettlementRepositoryLibrary.PartialSettlementTransaction
            memory partialSettlementTransaction
    ) external;

    /**
     * @dev Initiate a Coupon Settlement Transactions (issuer pays coupon to all holders)
     * NB: the tokens are not locked for this kind of transaction
     */
    function initiateCoupon(
        SettlementRepositoryLibrary.PartialSettlementTransaction[]
            calldata settlementTransaction,
        string calldata extendedData
    ) external;

    /**
     * @dev Cancels ongoing settlement transaction with id `settlementTransactionId`
     */
    function cancelSettlementTransaction(uint256 settlementTransactionId)
        external;

    /**
     * @dev Confirms that the payment for Settlement Transaction with id `settlementTransactionId` was correctly received
     * (by the settlement agent). The tokens are unlocked and transferred to the buyer. Settlement Transaction's status moves to Processed
     */
    function confirmPaymentReceived(uint256 settlementTransactionId) external;

    /**
     * @dev Confirms that the payment for Settlement Transaction with id `settlementTransactionId` was correctly transferred to the seller
     * (by the settlement agent). Settlement Transaction's status moves to Settled
     */
    function confirmPaymentTransferred(uint256 settlementTransactionId)
        external;

    /**
     * @dev Emitted when Subscription Settlement Transaction with id `settlementTransactionId` was initiated.
     */
    event SubscriptionInitiated(uint256 settlementTransactionId);
    /**
     * @dev Emitted when Trade Settlement Transaction with id `settlementTransactionId` was initiated.
     */
    event TradeInitiated(uint256 settlementTransactionId);
    /**
     * @dev Emitted when Redemption Settlement Transactions with ids `settlementTransactionIds` were initiated.
     */
    event RedemptionInitiated(uint256[] settlementTransactionIds);
    /**
     * @dev Emitted when Coupon Settlement Transactions with ids `settlementTransactionId`s were initiated.
     */
    event CouponInitiated(uint256[] settlementTransactionIds);
    /**
     * @dev Emitted when payment for Settlement Transaction with id `settlementTransactionId` was reported as received
     */
    event PaymentReceived(
        uint256 settlementTransactionId,
        SettlementRepositoryLibrary.OperationType settlementTransactionOperationType
    );
    /**
     * @dev Emitted when payment for Settlement Transaction with id `settlementTransactionId` was reported as transferred
     */
    event PaymentTransferred(
        uint256 settlementTransactionId,
        SettlementRepositoryLibrary.OperationType settlementTransactionOperationType
    );
    /**
     * @dev Emitted when Settlement Transaction with id `settlementTransactionId` was cancelled
     */
    event SettlementTransactionCancelled(uint256 settlementTransactionId);
}

// File: contracts/product/libraries/ApproveAdapterLibrary.sol



pragma solidity 0.8.17;

import "./EncodingUtils.sol";
import "./SecurityTokenBalancesLibrary.sol";
import "./TransferAdapterLibrary.sol";

import { ZeroAddressCheck, InvalidApproveStatus, OwnerHasOngoingApprove, ApproveDoesNotExists, InsufficientAllowance } from "../../utils/ProductErrorReporter.sol";

/**
 * @dev This library allows to handle two-step approves as described in IERC20Adapter
 */
library ApproveAdapterLibrary {
    using SecurityTokenBalancesLibrary for SecurityTokenBalancesLibrary.SecurityTokenBalances;
    using TransferAdapterLibrary for TransferAdapterLibrary.OngoingTransfers;

    /**
     * @dev The different status an approve request can have
     * Approve requests are initially in the `Created` state
     * If validated by the validator authority they move to the `Validated` state, which is a final state
     * If rejected by the validator authority they move to the `Rejected` state, which is a final state
     * 
     */
    enum ApproveStatus {
        Undefined,
        Created,
        Validated,
        Rejected
    }

    /**
     * @dev The details of an approve request
     */
    struct Approve {
        /**
        * @dev The address of the owner 
        */
        address owner;
        /**
        * @dev The address of the spender 
        */
        address spender;
        /**
        * @dev The number of tokens that the spender is allowed to spend
        */
        uint256 value;
        /**
        * @dev The status of the approve request
        */
        ApproveStatus status;
    }

    /**
     * @dev Structure containing all ongoing approve requests
     * Requests are indexed by the hash of their content
    */
    struct OngoingApproves {
        mapping(bytes32 => Approve) approves;
        /**
        * @dev Counter of requests, allowing all request to be unique
        */
        uint256 counter;
        /**
        * @dev Current allowances indexed by owner address, then by spender address
        */
        mapping(address => mapping(address => uint256)) allowances;
        /**
        * @dev Boolean indicating for a couple (owner,spender) whether there is an ongoing approve request
        * This enables to reject approve request really if there is already an ongoing approve request for the same (owner, spender), thus
        * avoiding complex issues
        */
        mapping(address => mapping(address => bool)) hasOngoingApprove;
    }

    /**
     * @dev Initiates an approve request
     * If the requested allowance is less than current allowance, the excess tokens are unlocked
     * If the requested allowance is more than current allowance, the excess tokens are locked
     * NB : as the allowance is first reset to 0, the allowance will stay 0 is the request is rejected.
     * This is by design and not a bug.
    */
    function addApprove(
        OngoingApproves storage self,
        SecurityTokenBalancesLibrary.SecurityTokenBalances
            storage securityTokenBalances,
        address owner,
        address spender,
        uint256 value
    ) public returns (bytes32) {
        if (owner == address(0)) revert ZeroAddressCheck();
        if (spender == address(0)) revert ZeroAddressCheck();
        uint256 currentAllowedAmount = self.allowances[owner][spender];
        if (currentAllowedAmount > value) {
            securityTokenBalances.unlock(owner, (currentAllowedAmount - value));
        } else if (currentAllowedAmount < value) {
            securityTokenBalances.lock(owner, (value - currentAllowedAmount));
        }
        self.allowances[owner][spender] = 0; // reset allowance
        bytes32 approveHash = EncodingUtils.encodeRequest(
            owner,
            spender,
            value,
            self.counter
        );
        self.approves[approveHash] = Approve(
            owner,
            spender,
            value,
            ApproveStatus.Created
        );
        self.hasOngoingApprove[owner][spender] = true;
        self.counter += 1;

        return approveHash;
    }

    /**
     * @dev Validates an approve request
     * Provided there is a matching approve request in state Created, the approve is performed (that is, the allowance is set to the target
     * value, and the approve request status moves to Validated)
    */
    function validateApprove(OngoingApproves storage self, bytes32 approveHash)
        public
    {
        Approve storage approve = self.approves[approveHash];
        if (approve.status == ApproveStatus.Undefined)
            revert ApproveDoesNotExists();
        if (approve.status != ApproveStatus.Created)
            revert InvalidApproveStatus();
        self.allowances[approve.owner][approve.spender] = approve.value;
        self.hasOngoingApprove[approve.owner][approve.spender] = false;
        self.approves[approveHash].status = ApproveStatus.Validated;
    }

    /**
     * @dev Rejects an approve request
     * Provided there is a matching approve request in state Created, the approve is cancelled (that is, the tokens are unlocked
     * , and the approve request status moves to Validated)
     * NB: after the request has be rejected, the allowance is 0 (and not the allowance that existed before the request was sent), this
     * is by design and not a bug
    */
    function rejectApprove(
        OngoingApproves storage self,
        SecurityTokenBalancesLibrary.SecurityTokenBalances
            storage securityTokenBalances,
        bytes32 approveHash
    ) public {
        Approve memory approve = self.approves[approveHash];
        if (approve.status == ApproveStatus.Undefined)
            revert ApproveDoesNotExists();
        if (approve.status != ApproveStatus.Created)
            revert InvalidApproveStatus();
        securityTokenBalances.unlock(approve.owner, approve.value);
        self.hasOngoingApprove[approve.owner][approve.spender] = false;
        self.approves[approveHash].status = ApproveStatus.Rejected;
    }

    /**
     * @dev Initiates a transferFrom request
     * This code is here to enable checking the allowance before accepting request
    */
    function transferFrom(
        OngoingApproves storage self,
        SecurityTokenBalancesLibrary.SecurityTokenBalances
            storage securityTokenBalances,
        TransferAdapterLibrary.OngoingTransfers storage ongoingTransfers,
        address spender,
        address from,
        address to,
        uint256 value
    ) public returns (bytes32) {
        uint256 currentAmount = self.allowances[from][spender];
        if (currentAmount < value) revert InsufficientAllowance();
        self.allowances[from][spender] -= value;
        bytes32 transferHash = ongoingTransfers.addTransfer(
            securityTokenBalances,
            from,
            to,
            value,
            true, // isTransferFrom
            spender
        );
        return transferHash;
    }

    /**
     * @dev Returns current allowance for `owner` and `spender`
    */
    function allowance(
        OngoingApproves storage self,
        address owner,
        address spender
    ) public view returns (uint256) {
        return self.allowances[owner][spender];
    }
}

// File: contracts/product/libraries/EncodingUtils.sol



pragma solidity 0.8.17;

library EncodingUtils {
    /**
     * @dev Computes the hash of a tranfer or approve request
     * Enables to index requests in mappings
     */
    function encodeRequest(
        address _from,
        address _to,
        uint256 _value,
        uint256 counter
    ) internal view returns (bytes32) {
        return
            keccak256(abi.encode(block.timestamp, _from, _to, _value, counter));
    }
}

// File: contracts/product/libraries/IterableBalances.sol



pragma solidity 0.8.17;

error KeyNotInBalances();

/**
 * @dev An iterable mapping of security token balances.
 * Handles total and locked balances (locked balance corresponds to tokens that are reserved for an ongoing Settlement Transaction)
 */
library IterableBalances {
    struct iterableBalances {
        mapping(address => Balance) balances;
        KeyFlag[] keys;
        uint256 size;
    }

    struct Balance {
        uint256 keyIndex;
        uint256 total;
        uint256 locked;
    }
    struct KeyFlag {
        address key;
        bool deleted;
    }

    function insert(
        iterableBalances storage self,
        address key,
        uint256 total
    ) public {
        uint256 keyIndex = self.balances[key].keyIndex;
        self.balances[key].total = total;

        if (keyIndex == 0) {
            keyIndex = self.keys.length;
            self.keys.push();
            self.balances[key].keyIndex = keyIndex + 1;
            self.keys[keyIndex].key = key;
            self.size++;
        }
    }

    function remove(iterableBalances storage self, address key) public {
        uint256 keyIndex = self.balances[key].keyIndex;

        if (keyIndex == 0) revert KeyNotInBalances();

        delete self.balances[key];
        self.keys[keyIndex - 1].deleted = true;
        self.size--;
    }

    function contains(iterableBalances storage self, address key)
        public
        view
        returns (bool)
    {
        return self.balances[key].keyIndex > 0;
    }

    function iterate_start(iterableBalances storage self)
        public
        view
        returns (uint256 keyIndex)
    {
        return iterate_next(self, type(uint256).max);
    }

    function iterate_valid(iterableBalances storage self, uint256 keyIndex)
        public
        view
        returns (bool)
    {
        return keyIndex < self.keys.length;
    }

    function iterate_next(iterableBalances storage self, uint256 keyIndex)
        public
        view
        returns (uint256 r_keyIndex)
    {
        unchecked {
            keyIndex++;
        }

        while (keyIndex < self.keys.length && self.keys[keyIndex].deleted) {
            keyIndex++;
        }

        return keyIndex;
    }

    function iterate_get(iterableBalances storage self, uint256 keyIndex)
        public
        view
        returns (
            address key,
            uint256 total,
            uint256 locked
        )
    {
        key = self.keys[keyIndex].key;
        total = self.balances[key].total;
        locked = self.balances[key].locked;
    }

}

// File: contracts/product/libraries/SecurityTokenBalancesLibrary.sol



pragma solidity 0.8.17;
import "./IterableBalances.sol";
import "@openzeppelin/contracts/utils/math/SafeMath.sol";

import { InsufficientDisposableBalance, InsufficientBalance, InsufficientLockedBalance } from "../../utils/ProductErrorReporter.sol";

/// @dev Models a address -> uint mapping where it is possible to iterate over all keys.
library SecurityTokenBalancesLibrary {
    using IterableBalances for IterableBalances.iterableBalances;
    using SafeMath for uint256;

    struct SecurityTokenBalances {
        address issuer;
        IterableBalances.iterableBalances iterableBalances;
    }
    modifier onlyWhenBalanceDisposable(
        SecurityTokenBalances storage self,
        address _from,
        uint256 _value
    ) {
        if (
            self.iterableBalances.balances[_from].total -
                self.iterableBalances.balances[_from].locked <
            _value
        ) revert InsufficientDisposableBalance();
        _;
    }
    struct Balance {
        address account;
        uint256 total;
        uint256 locked;
    }

    event Transfer(address indexed _from, address indexed _to, uint256 _value); // Only for erc20 explorer

    function setIssuer(SecurityTokenBalances storage self, address key) public {
        self.issuer = key;
    }

    function mint(
        SecurityTokenBalances storage self,
        address key,
        uint256 balance
    ) public {
        self.iterableBalances.insert(key, balance);
        emit Transfer(0x0000000000000000000000000000000000000000, key, balance);
    }

    function lock(
        SecurityTokenBalances storage self,
        address key,
        uint256 valueToLock
    ) public onlyWhenBalanceDisposable(self, key, valueToLock) {
        self.iterableBalances.balances[key].locked += valueToLock;
    }

    function unlock(
        SecurityTokenBalances storage self,
        address key,
        uint256 valueToUnlock
    ) public {
        if (self.iterableBalances.balances[key].total < valueToUnlock)
            revert InsufficientBalance();
        if (self.iterableBalances.balances[key].locked < valueToUnlock)
            revert InsufficientLockedBalance();

        self.iterableBalances.balances[key].locked -= valueToUnlock;
    }

    function burn(
        SecurityTokenBalances storage self,
        address _from,
        uint256 _value
    ) public onlyWhenBalanceDisposable(self, _from, _value) {
        self.iterableBalances.balances[_from].total -= _value;

        emit Transfer(_from, address(0), _value);
    }

    function transferLocked(
        SecurityTokenBalances storage self,
        address _from,
        address _to,
        uint256 _value
    ) external {
        unlock(self, _from, _value);

        self.iterableBalances.balances[_from].total -= _value;

        self.iterableBalances.insert(
            _to,
            self.iterableBalances.balances[_to].total + _value
        );

        emit Transfer(_from, _to, _value);
    }

    // should check this of any security issue?
    function transfer(
        SecurityTokenBalances storage self,
        address _from,
        address _to,
        uint256 _value
    ) external onlyWhenBalanceDisposable(self, _from, _value) {
        self.iterableBalances.balances[_from].total -= _value;

        self.iterableBalances.insert(
            _to,
            self.iterableBalances.balances[_to].total + _value
        );

        emit Transfer(_from, _to, _value);
    }

    // rename to getTotalBalance()
    function getBalance(SecurityTokenBalances storage self, address _address)
        external
        view
        returns (uint256 balance)
    {
        return self.iterableBalances.balances[_address].total;
    }

    function getAvailableBalance(
        SecurityTokenBalances storage self,
        address _address
    ) external view returns (uint256 balance) {
        return
            self.iterableBalances.balances[_address].total -
            self.iterableBalances.balances[_address].locked;
    }

    function getFullBalance(
        SecurityTokenBalances storage self,
        address _address
    ) external view returns (Balance memory value) {
        return
            Balance(
                _address,
                self.iterableBalances.balances[_address].total,
                self.iterableBalances.balances[_address].locked
            );
    }

    function getFullBalances(SecurityTokenBalances storage self)
        public
        view
        returns (Balance[] memory value)
    {
        address tokenHolder = address(0);
        uint256 balance;
        uint256 locked;
        uint256 balancesSize = self.iterableBalances.size;
        Balance[] memory addressBalanceArray = new Balance[](balancesSize);
        for (
            uint256 index = self.iterableBalances.iterate_start();
            self.iterableBalances.iterate_valid(index);
            index = self.iterableBalances.iterate_next(index)
        ) {
            (tokenHolder, balance, locked) = self.iterableBalances.iterate_get(
                index
            );
            addressBalanceArray[index] = Balance(tokenHolder, balance, locked);
        }

        return addressBalanceArray;
    }
}

// File: contracts/product/libraries/SettlementRepositoryLibrary.sol



pragma solidity 0.8.17;

import "./SecurityTokenBalancesLibrary.sol";

import { NotSupportedTrustedToken, InvalidSettlementTransactionStatus, SettlementTransactionAlreadyExists } from "../../utils/ProductErrorReporter.sol";


/**
 * @dev This library handles the Settlement Transaction Repository which is the container for all
 * Settlement Transactions for a given product
 */
library SettlementRepositoryLibrary {
    using SecurityTokenBalancesLibrary for SecurityTokenBalancesLibrary.SecurityTokenBalances;
    using SettlementRepositoryLibrary for SettlementRepositoryLibrary.SettlementTransactionRepository;

    /**
     * @dev The different status a Settlement Transaction can have
     * Settlement Transactions are initially in the `Created` state.
     * Once correctly initiated they move to the Acknowledged state.
     * When the payment of the cash leg (if any) is received by the settlement agent, they move to the Processed state
     * When the payment of the cash leg is transferred to the receiving end, they move to the Settled state, which is a final state.
     * Whenever a Settlement Transaction is cancelled, it moves to the Cancelled state, which is a final state
     */
    enum SettlementTransactionStatus {
        Undefined,
        Created,
        Acknowledged,
        Processed,
        Settled,
        Cancelled
    }

    /**
     * @dev The different operation types
     * Operation types is used to keep track of the type of business operation that a Settlement Transaction is part of.
     * Some business operations(e.g. Redemption) lead to several Settlement Transactions
     */
    enum OperationType {
        Undefined,
        Subscription,
        Redemption,
        Trade,
        Coupon,
        ExecuteTransfer
    }

    /**
     * @dev The actual Settlement Transaction Repository, which maps Settlement Transaction ids to Settlement Transaction details
     */
    struct SettlementTransactionRepository {
        mapping(uint256 => SettlementTransaction) settlementTransactionById; // mapping ( settlementtransactionId => settlementtransaction)
    }

    /**
     * @dev The details of a Settlement Transaction
     * NB: actually these are only the details that can be publicly disclosed on the blockchain
     * The confidential details are available on a public server called the STR, which URL is available in the strUrl field of the products
     */
    struct SettlementTransaction {
        /**
         * @dev The UUID of the Settlement Transaction(as an integer)
        */
        uint256 txId;
        /**
         * @dev Optional id of the business operation this Settlement Transaction is part of.
        */
        uint256 operationId;
        /**
         * @dev The address of counterparty that delivers the security tokens(a.k.a product)
        */
        address deliverySenderAccountNumber;
        /**
         * @dev The address of counterparty that receives the security tokens(a.k.a product)
        */
        address deliveryReceiverAccountNumber;
        /**
         * @dev The quantity of security tokens(a.k.a product) that should be delivered
        */
        uint256 deliveryQuantity;
        /**
         * @dev Hash of the Settlement Transaction details
        */
        string txHash;
        /**
         * @dev Current status of the Settlement Transaction
        */
        SettlementTransactionStatus status;
        /**
         * @dev The type of the business operation this Settlement Transaction is part of.
        */
        OperationType operationType;
    }

    /**
     * @dev Partial details of a Settlement Transaction that are passed when initiating the transaction
     */
    struct PartialSettlementTransaction {
        /**
         * @dev The UUID of the Settlement Transaction(as an integer)
        */
        uint256 txId;
        /**
         * @dev Optional id of the business operation this Settlement Transaction is part of.
        */
        uint256 operationId;
        /**
         * @dev The address of counterparty that delivers the security tokens(a.k.a product)
         * e.g the issuer for a Subscription operation, the investor for a Redemption operation, the seller for a Trade operation
        */
        address deliverySenderAccountNumber;
        /**
         * @dev The address of counterparty that receives the security tokens(a.k.a product)
         *  e.g the investor for a Subscription operation, the issuer for a Redemption operation, the buyer for a Trade operation
        */
        address deliveryReceiverAccountNumber;
        /**
         * @dev The quantity of security tokens(a.k.a product) that should be delivered
        */
        uint256 deliveryQuantity;
        /**
         * @dev Hash of the Settlement Transaction details
        */
        string txHash;
    }

    /**
     * @dev Returns the details of the Settlement Transaction with id `id`
     */
    function getSettlementTransactionById(
        SettlementTransactionRepository storage settlementTransactionRepository,
        uint256 id
    ) public view returns (SettlementTransaction memory) {
        SettlementTransaction
            memory settlementTransaction = settlementTransactionRepository
                .settlementTransactionById[id];

        return settlementTransaction;
    }

    /**
     * @dev Updates the status of Settlement Transaction with id `txId`
     */
    function setSettlementTransactionStatus(
        SettlementTransactionRepository storage settlementTransactionRepository,
        uint256 txId,
        SettlementTransactionStatus status
    ) internal {
        if (status == SettlementTransactionStatus.Undefined)
            revert InvalidSettlementTransactionStatus();

        SettlementTransaction
            storage settlementTransaction = settlementTransactionRepository
                .settlementTransactionById[txId];
        settlementTransaction.status = status;
    }

    /**
     * @dev Adds Settlement Transaction with given details to the repository.
     * NB : status is initialized to `Created`
     */
    function createSettlementTransaction(
        SettlementTransactionRepository storage settlementTransactionRepository,
        PartialSettlementTransaction memory partialSettlementTransaction,
        OperationType operationType
    )
        internal
    {
        if (
            settlementTransactionRepository
                .settlementTransactionById[partialSettlementTransaction.txId]
                .txId == partialSettlementTransaction.txId
        ) revert SettlementTransactionAlreadyExists();

        SettlementTransaction
            memory newSettlementTransaction = SettlementTransaction({
                txId: partialSettlementTransaction.txId,
                operationId: partialSettlementTransaction.operationId,
                deliverySenderAccountNumber: partialSettlementTransaction
                    .deliverySenderAccountNumber,
                deliveryReceiverAccountNumber: partialSettlementTransaction
                    .deliveryReceiverAccountNumber,
                deliveryQuantity: partialSettlementTransaction.deliveryQuantity,
                txHash: partialSettlementTransaction.txHash,
                status: SettlementTransactionStatus.Created,
                operationType: operationType
            });
        settlementTransactionRepository.settlementTransactionById[
            partialSettlementTransaction.txId
        ] = newSettlementTransaction;
    }
}

// File: contracts/product/libraries/SettlementWorkflowLibrary.sol



pragma solidity 0.8.17;

import "./SettlementRepositoryLibrary.sol";
import "./SecurityTokenBalancesLibrary.sol";

import { SettlementTransactionNotInAcknowledgedState, DeliveryReceiverAccountNumberNotMatchTokenIssuer, InvalidSettlementTransactionStatus } from "../../utils/ProductErrorReporter.sol";

/**
 * @dev This library handles the details of the different Settlement Transaction workflows
 */
library SettlementWorkflowLibrary {
    using SettlementRepositoryLibrary for SettlementRepositoryLibrary.SettlementTransactionRepository;
    using SecurityTokenBalancesLibrary for SecurityTokenBalancesLibrary.SecurityTokenBalances;

    /**
     * @dev Mutualizes code for initializing DVP-type Settlement Transactions.
     * DVP means Delivery Versus Payment, meaning that there are both a token leg and a cash leg
     * In this case the tokens have to be locked while awaiting payment confirmation by the settlement agent
     */
    function initiateDVP(
        SettlementRepositoryLibrary.SettlementTransactionRepository
            storage settlementTransactionRepository,
        SecurityTokenBalancesLibrary.SecurityTokenBalances
            storage securityTokenBalances,
        uint256 settlementTransactionId
    ) public {
        SettlementRepositoryLibrary.SettlementTransaction
            memory st = settlementTransactionRepository
                .getSettlementTransactionById(settlementTransactionId);

        securityTokenBalances.lock(
            st.deliverySenderAccountNumber,
            st.deliveryQuantity
        );

        settlementTransactionRepository.setSettlementTransactionStatus(
            settlementTransactionId,
            SettlementRepositoryLibrary.SettlementTransactionStatus.Acknowledged
        );
    }

    /**
     * @dev Initiate a Subscription Settlement Transaction (counterparty buys product on primary market from the product's issuer)
     * The tokens are locked waiting for the cash leg to settle (see functions confirmPaymentReceived/confirmPaymentTransferred)
     */
    function initiateSubscription(
        SettlementRepositoryLibrary.SettlementTransactionRepository
            storage settlementTransactionRepository,
        SecurityTokenBalancesLibrary.SecurityTokenBalances
            storage securityTokenBalances,
        SettlementRepositoryLibrary.PartialSettlementTransaction
            memory partialSettlementTransaction
    ) public {
        settlementTransactionRepository.createSettlementTransaction(
            partialSettlementTransaction,
            SettlementRepositoryLibrary.OperationType.Subscription
        );

        initiateDVP(
            settlementTransactionRepository,
            securityTokenBalances,
            partialSettlementTransaction.txId
        );
    }

    /**
     * @dev Execute unilateral Transfer Settlement Transactions 
     * As there is no cash leg to settle, the tokens are transfered right away and the Settlement Transaction is marked as Settled
     */
    function executeTransfer(
        SettlementRepositoryLibrary.SettlementTransactionRepository
            storage settlementTransactionRepository,
        SecurityTokenBalancesLibrary.SecurityTokenBalances
            storage securityTokenBalances,
        SettlementRepositoryLibrary.PartialSettlementTransaction
            memory partialSettlementTransaction
    ) public {
        settlementTransactionRepository.createSettlementTransaction(
            partialSettlementTransaction,
            SettlementRepositoryLibrary.OperationType.ExecuteTransfer
        );
        securityTokenBalances.transfer(
            partialSettlementTransaction.deliverySenderAccountNumber,
            partialSettlementTransaction.deliveryReceiverAccountNumber,
            partialSettlementTransaction.deliveryQuantity
        );
    }

    /**
     * @dev Initiate a Trade Settlement Transaction (one counterparty buys, the other sells)
     * The tokens are locked waiting for the cash leg to settle (see functions confirmPaymentReceived/confirmPaymentTransferred)
     */
    function initiateTrade(
        SettlementRepositoryLibrary.SettlementTransactionRepository
            storage settlementTransactionRepository,
        SecurityTokenBalancesLibrary.SecurityTokenBalances
            storage securityTokenBalances,
        SettlementRepositoryLibrary.PartialSettlementTransaction
            memory partialSettlementTransaction
    ) public {
        settlementTransactionRepository.createSettlementTransaction(
            partialSettlementTransaction,
            SettlementRepositoryLibrary.OperationType.Trade
        );

        initiateDVP(
            settlementTransactionRepository,
            securityTokenBalances,
            partialSettlementTransaction.txId
        );
    }

    /**
     * @dev Initiate a Redemption Settlement Transactions (issuer redeems product to all holders and gets the tokens back)
     * The tokens are locked waiting for the cash leg to settle (see functions confirmPaymentReceived/confirmPaymentTransferred)
     */
    function initiateRedemption(
        SettlementRepositoryLibrary.SettlementTransactionRepository
            storage settlementTransactionRepository,
        SecurityTokenBalancesLibrary.SecurityTokenBalances
            storage securityTokenBalances,
        SettlementRepositoryLibrary.PartialSettlementTransaction[]
            memory partialSettlementTransactions
    ) public {
        for (uint256 i = 0; i < partialSettlementTransactions.length; i++) {
            if (
                securityTokenBalances.issuer !=
                partialSettlementTransactions[i].deliveryReceiverAccountNumber
            ) revert DeliveryReceiverAccountNumberNotMatchTokenIssuer();

            settlementTransactionRepository.createSettlementTransaction(
                partialSettlementTransactions[i],
                SettlementRepositoryLibrary.OperationType.Redemption
            );

            initiateDVP(
                settlementTransactionRepository,
                securityTokenBalances,
                partialSettlementTransactions[i].txId
            );
        }
    }

    /**
     * @dev Initiate a Coupon Settlement Transactions (issuer pays coupon to all holders)
     * NB: the tokens are not locked for this kind of transaction
     */
    function initiateCoupon(
        SettlementRepositoryLibrary.SettlementTransactionRepository
            storage settlementTransactionRepository,
        SettlementRepositoryLibrary.PartialSettlementTransaction[]
            memory partialSettlementTransactions
    ) public {
        for (uint256 i = 0; i < partialSettlementTransactions.length; i++) {
            settlementTransactionRepository.createSettlementTransaction(
                partialSettlementTransactions[i],
                SettlementRepositoryLibrary.OperationType.Coupon
            );
            settlementTransactionRepository.setSettlementTransactionStatus(
                partialSettlementTransactions[i].txId,
                SettlementRepositoryLibrary
                    .SettlementTransactionStatus
                    .Acknowledged
            );
        }
    }

    /**
     * @dev Cancels ongoing settlement transaction with id `settlementTransactionId`
     * Unlocks tokens
     * Fails if the settlement transaction is not in the Acknowledged state.
     */
    function cancelSettlementTransaction(
        SettlementRepositoryLibrary.SettlementTransactionRepository
            storage settlementTransactionRepository,
        SecurityTokenBalancesLibrary.SecurityTokenBalances
            storage securityTokenBalances,
        uint256 settlementTransactionId
    ) public {
        SettlementRepositoryLibrary.SettlementTransaction
            memory settlementTransaction = settlementTransactionRepository
                .settlementTransactionById[settlementTransactionId];

        if (
            settlementTransaction.status !=
            SettlementRepositoryLibrary.SettlementTransactionStatus.Acknowledged
        ) revert SettlementTransactionNotInAcknowledgedState();

        securityTokenBalances.unlock(
            settlementTransaction.deliverySenderAccountNumber,
            settlementTransaction.deliveryQuantity
        );
        settlementTransactionRepository.setSettlementTransactionStatus(
            settlementTransactionId,
            SettlementRepositoryLibrary.SettlementTransactionStatus.Cancelled
        );
    }
}

// File: contracts/product/libraries/TransferAdapterLibrary.sol



pragma solidity 0.8.17;

import "./EncodingUtils.sol";
import "./SecurityTokenBalancesLibrary.sol";

import { ZeroAddressCheck, TransferRequestNotFound, InvalidTransferRequestStatus } from "../../utils/ProductErrorReporter.sol";

/**
 * @dev This library allows to handle two-step transfers as described in IERC20Adapter
 */
library TransferAdapterLibrary {
    using SecurityTokenBalancesLibrary for SecurityTokenBalancesLibrary.SecurityTokenBalances;

    /**
     * @dev The different status a transfer request can have
     * Transfer requests are initially in the `Created` state
     * If validated by the validator authority they move to the `Validated` state, which is a final state
     * If rejected by the validator authority they move to the `Rejected` state, which is a final state
     * 
     */
    enum TransferStatus {
        Undefined,
        Created,
        Validated,
        Rejected
    }

    /**
     * @dev The details of a transfer request
     */
    struct Transfer {
        /**
        * @dev The source address of the transfer 
        */
        address from;
        /**
        * @dev The destination address of the transfer 
        */
        address to;
        /**
        * @dev The number of tokens to transfer
        */
        uint256 value;
        /**
        * @dev The status of the transfer request
        */
        TransferStatus status;
        /**
        * @dev Whether this is a transferFrom request (i.e. allowance will be consumed)
        */
        bool isTransferFrom;
        /**
        * @dev Address of the spender address, in case of transferFrom request
        */
        address spender;
    }

    /**
     * @dev Structure containing all ongoing transfer requests
     * Requests are indexed by the hash of their content
    */
    struct OngoingTransfers {
        mapping(bytes32 => Transfer) transfers;
        uint256 counter;
    }

    /**
     * @dev Initiates a transfer request
     * If simple transfer request, the tokens are locked, awaiting either validation or rejection by the validator authority
     * If transferFrom, the tockens where already locked when the approve request was done
    */
    function addTransfer(
        OngoingTransfers storage self,
        SecurityTokenBalancesLibrary.SecurityTokenBalances
            storage securityTokenBalances,
        address from,
        address to,
        uint256 value,
        bool isTransferFrom,
        address spender
    ) public returns (bytes32) {
        if (from == address(0)) revert ZeroAddressCheck();
        if (to == address(0)) revert ZeroAddressCheck();
        if (!isTransferFrom) {
            securityTokenBalances.lock(from, value);
        }
        bytes32 transferHash = EncodingUtils.encodeRequest(
            from,
            to,
            value,
            self.counter
        );
        self.transfers[transferHash] = Transfer(
            from,
            to,
            value,
            TransferStatus.Created,
            isTransferFrom,
            spender
        );
        self.counter += 1;
        return transferHash;
    }

    /**
     * @dev Validates a transfer request
     * Provided there is a matching transfer request in state Created, the transfer is performed (that is, the tokens are
     * transferred to the destination address, and unlocked, and the transfer request status moves to Validated)
    */
    function validateTransfer(
        OngoingTransfers storage self,
        SecurityTokenBalancesLibrary.SecurityTokenBalances
            storage securityTokenBalances,
        bytes32 transferHash
    ) public {
        Transfer storage transfer = self.transfers[transferHash];
        if (transfer.status == TransferStatus.Undefined)
            revert TransferRequestNotFound();
        if (transfer.status != TransferStatus.Created)
            revert InvalidTransferRequestStatus();
        securityTokenBalances.transferLocked(
            transfer.from,
            transfer.to,
            transfer.value
        );
        transfer.status = TransferStatus.Validated;
    }

    /**
     * @dev Rejects a transfer request
     * Provided there is a matching transfer request in state Created, the transfer is cancelled (that is, the transfer request staus moves 
     * to Rejected)
     * NB : due to the complexity of the token unlocking logic (depending on whether the transfer request was a transferFrom or not, either
     * the tokens should be unlocked (simple transfer) or the previous allowance should be restored (transferFrom)) and to avoid complex 
     * inter-dependency between ApproveAdapterLibrary and TransferAdapterLibrary, this logic is perform directly in Product.rejectTransfer
    */
    function rejectTransfer(OngoingTransfers storage self, bytes32 transferHash)
        public
    {
        Transfer storage transfer = self.transfers[transferHash];
        if (transfer.status == TransferStatus.Undefined)
            revert TransferRequestNotFound();
        if (transfer.status != TransferStatus.Created)
            revert InvalidTransferRequestStatus();
        transfer.status = TransferStatus.Rejected;
    }
}

// File: contracts/product/Product.sol



pragma solidity 0.8.17;

import "./libraries/SecurityTokenBalancesLibrary.sol";
import "./libraries/IterableBalances.sol";
import "./libraries/SettlementRepositoryLibrary.sol";
import "./libraries/SettlementWorkflowLibrary.sol";
import "./libraries/TransferAdapterLibrary.sol";
import "./libraries/ApproveAdapterLibrary.sol";
import "./interfaces/IProduct.sol";
import "./interfaces/ISettlement.sol";
import "./interfaces/IERC20Adapter.sol";
import "../registry/interfaces/IProductRegistry.sol";
import "../utils/Ownable.sol";
import "@openzeppelin/contracts/utils/introspection/ERC165.sol";
import "../utils/ProductErrorReporter.sol";

error InvalidRegistryAddress();


/**
 * @dev This contract represents a CAST-compatible financial product
 * It includes a number of basic financial information about the product (common to all products)
 * It also contains extended information specific to this product, as a JSON string in field extendedDataJson
 * It also includes the methods needed to handle Settlement Transactions on this instrument(subscriptions on primary market, secondary
 * market trades as well as coupons and redemption) -> see ISettlement interface
 * It also includes the methods needed to interact (almost) as an ERC20 token, enable to interact with DeFi protocols. -> see IERC20Adapter 
 * interface
 * Supports ERC165
 */
contract Product is
    ISettlement,
    IProduct,
    IERC20Adapter,
    ERC165,
    Ownable
{
    using SecurityTokenBalancesLibrary for SecurityTokenBalancesLibrary.SecurityTokenBalances;
    using SettlementRepositoryLibrary for SettlementRepositoryLibrary.SettlementTransactionRepository;
    using TransferAdapterLibrary for TransferAdapterLibrary.OngoingTransfers;
    using ApproveAdapterLibrary for ApproveAdapterLibrary.OngoingApproves;
    // Basic Informations
    /**
     * @dev The product's currency
     */
    string public currency;
    /**
     * @dev The product's denomination in cents
     */
    uint256 public denominationCent;
    /**
     * @dev The product's maturity date (unix timestamp)
     */
    uint256 public maturityDate;
    /**
     * @dev The product's governing law
     */
    string public governingLaw;
    /**
     * @dev The product's guarantor legal entity
     */
    LegalEntity public guarantor;
    /**
     * @dev The product's isin code
     */
    string public isinCode;
    /**
     * @dev The product's issuer legal entity
     */
    LegalEntity public issuer;
    /**
     * @dev The product's quotation market
     */
    string public listing;
    /**
     * @dev The product's name
     */
    string public name;
    /**
     * @dev The product's paying agent legal entity
     */
    LegalEntity public payingAgent;
    /**
     * @dev The product's type code (e.g. sgforge.com/bond/V1)
     */
    string public productType;
    /**
     * @dev The product's registrar legal entity
     */
    LegalEntity public registrar;
    /**
     * @dev The product's settlement agent legal entity
     */
    LegalEntity public settlementAgent;
    /**
     * @dev The product's symbol
     */
    string public symbol;
    /**
     * @dev The product's state
     */
    ProductState public state;
    /**
     * @dev This URL where the Settlement Transaction Repository can be reached
     * The STR can provided the confidential part of the Settlement Transactions to the counterparts (authentication with ethereum private key)
     */
    string public strUrl;
    /**
     * @dev The issuer's issuance program this product is part of
     */
    string public program;
    /**
     * @dev This product's series number
     */
    string public series;
    /**
     * @dev This product's note status
     */
    string public noteStatus;
    /**
     * @dev JSON string of extended information about the product. Can be roughly anything.
     * Will be used to hold financial information specific to a some products.
     */
    string public extendedDataJson;
    /**
     * @dev String holding ESG information about the product (JSON format)
     */
    string public esg;
    /**
     * @dev This product's form of the node (e.g. Registered)
     */
    string public formOfNote;
    /**
     * @dev This product's FIGI code
     */
    string public figi;
    /**
     * @dev The address of a previous contract that is replaced with this one (allows to keep track of successive contract migrations)
     */
    address public previousContract;

    /**
     * @dev Extended data(JSON string) associated with specific business operations
     */
    mapping(uint256 => string) public operationExtendedDataJson;

    /**
     * @dev The product's balances. Handles total and locked balance.
     */
    SecurityTokenBalancesLibrary.SecurityTokenBalances
        private securityTokenBalances;

    /**
     * @dev The product's settlement transaction repository
     * Contains all Settlement Transactions involving this product.
     */
    SettlementRepositoryLibrary.SettlementTransactionRepository
        private settlementTransactionRepository;

    /**
     * @dev Ongoing two-steps transfers.
     * See IERC20Adapter
     */
    TransferAdapterLibrary.OngoingTransfers private ongoingTransfers;
    /**
     * @dev Ongoing two-steps approves
     * See IERC20Adapter
     */
    ApproveAdapterLibrary.OngoingApproves private ongoingApproves;

    /**
     * @dev The version number of this smart contract.
     */
    string public constant version = "1.1";

    // Tranches related informations
    Tranche[] public tranches;
    /**
     * @dev The product total issued nominal amount(includes all tranches)
     */
    uint256 public seriesNominalAmountCent;
    /**
     * @dev The product outstanding nominal amount(includes all not-burnt tokens)
     */
    uint256 public seriesOutstandingNominalAmountCent;
    /**
     * @dev The product redeemed amount(i.e. burnt tokens)
     */
    uint256 public seriesRedeemedAmountCent;
    /**
     * @dev The total number of (not burnt) tokens
     */
    uint256 public totalSupply;

    /**
     * @dev The different states a product can have
     * Product are initially in the `Created` state.
     * Once at list one subscription operation has been initiated, they move to the `Running` state.
     * Once a redemption operation has been initiated, they move to the `Redeemed` state, which is the product's final state.
     * Once a product is in the `Redeemed` state, no more Settlement Transaction may be initiated on this product.
     * 
     */
    enum ProductState {
        Undefined,
        Created,
        Running,
        Redeemed
    }

    constructor(BasicProductInput memory basicProductInput) Ownable(basicProductInput.registrar.account){
        
        isinCode = basicProductInput.isinCode;
        name = basicProductInput.name;
        symbol = basicProductInput.symbol;
        denominationCent = basicProductInput.denominationCent;
        maturityDate = basicProductInput.maturityDate;
        governingLaw = basicProductInput.governingLaw;
        listing = basicProductInput.listing;
        state = ProductState.Created;
        currency = basicProductInput.currency;
        registrar = LegalEntity(
            basicProductInput.registrar.lei,
            basicProductInput.registrar.account
        );
        if(basicProductInput.settlementAgent.account == address(0))
            revert ZeroAddressCheck();
        settlementAgent = LegalEntity(
            basicProductInput.settlementAgent.lei,
            basicProductInput.settlementAgent.account
        );
        payingAgent = LegalEntity(
            basicProductInput.payingAgent.lei,
            basicProductInput.payingAgent.account
        );
        issuer = LegalEntity(
            basicProductInput.issuer.lei,
            basicProductInput.issuer.account
        );
        securityTokenBalances.setIssuer(issuer.account);
        guarantor = LegalEntity(
            basicProductInput.guarantor.lei,
            basicProductInput.guarantor.account
        );
        strUrl = basicProductInput.strUrl;
        productType = basicProductInput.productType;
        program = basicProductInput.program;
        series = basicProductInput.series;
        noteStatus = basicProductInput.noteStatus;
        formOfNote = basicProductInput.formOfNote;
        extendedDataJson = basicProductInput.extendedDataJson;
        esg = basicProductInput.esg;
        figi = basicProductInput.figi;
        previousContract = basicProductInput.previousContract;

        for (uint256 i = 0; i < basicProductInput.initialTranches.length; i++) {
            internalCreateTranche(basicProductInput.initialTranches[i]);
        }

        if (basicProductInput.initialBalances.length > 0) {
            uint256 balanceSupply = 0;
            for (
                uint256 i = 0;
                i < basicProductInput.initialBalances.length;
                i++
            ) {
                uint256 value = basicProductInput.initialBalances[i].balance;
                address to = basicProductInput.initialBalances[i].account;

                securityTokenBalances.mint(to, value);
                balanceSupply += value;
            }
            if (balanceSupply != totalSupply)
                revert TotalSupplyNotMatchTotalInitialSupply();
        } else {
            securityTokenBalances.mint(issuer.account, totalSupply);
        }
    }

    /**
     * @dev ERC165. Indicates that the product implements the ISettlement,IERC20Adapter and IProduct interfaces.
     */
    function supportsInterface(bytes4 interfaceId)
        override
        public
        view
        returns (bool)
    {
        return
            interfaceId == type(ISettlement).interfaceId ||
            interfaceId == type(IERC20Adapter).interfaceId ||
            interfaceId == type(IProduct).interfaceId ||
            super.supportsInterface(interfaceId);
    }

    // Basic Token
    /**
     * @dev Shortcut to the account of the issuer legal entity
     */
    function issuerAccount() external view returns (address) {
        return issuer.account;
    }

    /**
     * @dev Shortcut to the Legal Entity ID of the issuer legal entity
     */
    function issuerLEI() external view returns (string memory) {
        return issuer.lei;
    }

    /**
     * @dev Shortcut to the account of the settlement agent legal entity
     */
    function settlementAgentAccount() external view returns (address) {
        return settlementAgent.account;
    }

    /**
     * @dev Shortcut to the Legal Entity ID of the settlement agent legal entity
     */
    function settlementAgentLEI() external view returns (string memory) {
        return settlementAgent.lei;
    }

    /**
     * @dev Shortcut to the account of the paying agent legal entity
     */
    function payingAgentAccount() external view returns (address) {
        if (payingAgent.account == address(0)) revert PayingAgentNotSet();
        return payingAgent.account;
    }

    /**
     * @dev Shortcut to the Legal Entity ID of the paying agent legal entity
     */
    function payingAgentLEI() external view returns (string memory) {
        if (bytes(payingAgent.lei).length == 0) revert PayingAgentNotSet();
        return payingAgent.lei;
    }

    /**
     * @dev Shortcut to the account of the registrar legal entity
     */
    function registrarAccount() external view returns (address) {
        return registrar.account;
    }

    /**
     * @dev Shortcut to the Legal Entity ID of the registrar legal entity
     */
    function registrarLEI() external view returns (string memory) {
        return registrar.lei;
    }

    /**
     * @dev Shortcut to the account of the guarantor legal entity
     */
    function guarantorAccount() external view returns (address) {
        if (guarantor.account == address(0)) revert GuarantorNotSet();
        return guarantor.account;
    }

    /**
     * @dev Shortcut to the Legal Entity ID of the guarantor legal entity
     */
    function guarantorLEI() external view returns (string memory) {
        if (bytes(guarantor.lei).length == 0) revert GuarantorNotSet();
        return guarantor.lei;
    }

    /**
     * @dev Return details of all tranches
     */
    function getTranches() external view returns (Tranche[] memory) {
        return tranches;
    }

    // Modifiers
    /**
     * @dev Checks the product is not redeemed yet
     */
    modifier onlyNotRedeemedProduct() {
        if (state >= ProductState.Redeemed) revert ProductIsFullyRedeemed();
        _;
    }
    /**
     * @dev Checks there is no ongoing approve request for the same spender
     */
    modifier onlyOnAvailableSpender(address spender) {
        if (ongoingApproves.hasOngoingApprove[msg.sender][spender])
            revert OwnerHasOngoingApprove();
        _;
    }

    /**
     * @dev Used to protect methods that only the product's settlement agent can call
     */
    modifier settlerOnly() {
        if (msg.sender != settlementAgent.account) revert UnauthorizedSettler();
        _;
    }

    /**
     * @dev Allows to change the product's extended data descriptor
     * NB: Only the registrar can call this method
     */
    function setExtendeDataJson(string memory _extendedDataJson)
        external
        onlyRegistrar
    {
        extendedDataJson = _extendedDataJson;
    }

    /**
     * @dev Allows to change the product's ESG data descriptor
     * NB: Only the registrar can call this method
     */
    function setEsg(string memory _esg) external onlyRegistrar {
        esg = _esg;
    }

    // [ERC-20] Only
    function balanceOf(address _owner) public view returns (uint256 balance) {
        return securityTokenBalances.getBalance(_owner);
    }

    /**
     * @dev Initiates two-step simple transfer(cf IERC20Adapter) and sends corresponding `TransferRequest` event
     */
    function transfer(address _to, uint256 _value)
        external
        virtual
        override
        returns (bool)
    {
        if (_value == 0) revert ZeroValueCheck();
        if (_to == msg.sender) revert InvalidReceiver();
        bytes32 transferHash = ongoingTransfers.addTransfer(
            securityTokenBalances,
            msg.sender,
            _to,
            _value,
            false,
            address(0)
        );
        emit Transfer(msg.sender, _to, 0); // Required by https://eips.ethereum.org/EIPS/eip-20#transfer
        emit TransferRequest(transferHash, msg.sender, _to, _value);
        return true;
    }

    /**
     * @dev Initiates two-step approve(cf IERC20Adapter) and sends corresponding `ApproveRequest` event
     */
    function approve(address _spender, uint256 _value)
        external
        virtual
        override
        onlyOnAvailableSpender(_spender)
        returns (bool)
    {
        bytes32 approveHash = ongoingApproves.addApprove(
            securityTokenBalances,
            msg.sender,
            _spender,
            _value
        );
        emit ApproveRequest(approveHash, msg.sender, _spender, _value);
        return true;
    }

    /**
     * @dev Validates two-step transfer(cf IERC20Adapter) and sends corresponding `TransferValidated` event
     * NB: Only the registrar can call this method
     */
    function validateTransfer(bytes32 transferHash)
        external
        override
        onlyRegistrar
        returns (bool)
    {
        ongoingTransfers.validateTransfer(securityTokenBalances, transferHash);
        emit TransferValidated(transferHash);
        return true;
    }

    /**
     * @dev Rejects two-step transfer(cf IERC20Adapter) and sends corresponding `TransferRejected` event
     * Unlocks token if simple transfer
     * Restore allowance if tranferFrom
     * NB: Only the registrar can call this method
     */
    function rejectTransfer(bytes32 transferHash)
        external
        override
        onlyRegistrar
        returns (bool)
    {
        ongoingTransfers.rejectTransfer(transferHash);
        TransferAdapterLibrary.Transfer
            memory transferRequest = ongoingTransfers.transfers[transferHash];
        if (transferRequest.isTransferFrom) {
            ongoingApproves.allowances[transferRequest.from][
                transferRequest.spender
            ] += transferRequest.value;
        } else {
            securityTokenBalances.unlock(
                transferRequest.from,
                transferRequest.value
            );
        }
        emit TransferRejected(transferHash);
        return true;
    }

    /**
     * @dev Validates two-step approve(cf IERC20Adapter) and sends corresponding `ApproveValidated` event
     * NB: Only the registrar can call this method
     */
    function validateApprove(bytes32 approveHash)
        external
        override
        onlyRegistrar
        returns (bool)
    {
        ongoingApproves.validateApprove(approveHash);
        ApproveAdapterLibrary.Approve memory approveRequest = ongoingApproves
            .approves[approveHash];
        emit Approval(
            approveRequest.owner,
            approveRequest.spender,
            approveRequest.value
        );
        emit ApproveValidated(approveHash);
        return true;
    }


    /**
     * @dev Rejects two-step transfer(cf IERC20Adapter) and sends corresponding `ApproveRejected` event
     * NB: Only the registrar can call this method
     */
    function rejectApprove(bytes32 approveHash)
        external
        override
        onlyRegistrar
        returns (bool)
    {
        ongoingApproves.rejectApprove(securityTokenBalances, approveHash);
        emit ApproveRejected(approveHash);
        return true;
    }

    /**
     * @dev Returns current allowance for provided `owner` and `spender`
     */
    function allowance(address owner, address spender)
        external
        view
        override
        returns (uint256)
    {
        return ongoingApproves.allowance(owner, spender);
    }

    /**
     * @dev Initiates two-step transferFrom(cf IERC20Adapter) and sends corresponding `TransferRequest` event
     */
    function transferFrom(
        address from,
        address to,
        uint256 amount
    ) public virtual override returns (bool) {
        bytes32 transferHash = ongoingApproves.transferFrom(
            securityTokenBalances,
            ongoingTransfers,
            msg.sender,
            from,
            to,
            amount
        );
        emit TransferRequest(transferHash, msg.sender, to, amount);
        return true;
    }

    /**
     * @dev Security tokens are not divisible.
     * Only an integer number of tokens can be manipulated
     */
    function decimals() public pure returns (uint8) {
        return 0;
    }

    /**
     * @dev Burn the given `quantity` of tokens
     * The product's totalSupply is updated accordingly.
     * NB : only the product's registrar can call this method
     */
    function burn(uint256 quantity) public onlyRegistrar {
        internalBurn(issuer.account, quantity);        
    }

    function internalBurn(address account, uint256 quantity) internal {
        securityTokenBalances.burn(account, quantity);
        seriesRedeemedAmountCent += quantity * denominationCent;
        recomputeTotalSupply();
    }

    // IBasicToken
    /**
     * @dev Returns all balances as an array of Balance structures(including total and locked balance)
     */
    function getFullBalances()
        public
        view
        returns (SecurityTokenBalancesLibrary.Balance[] memory value)
    {
        return securityTokenBalances.getFullBalances();
    }

    /**
     * @dev Returns balance of `owner` as a Balance structures(including total and locked balance)
     */
    function getFullBalance(address owner)
        public
        view
        returns (SecurityTokenBalancesLibrary.Balance memory value)
    {
        return securityTokenBalances.getFullBalance(owner);
    }

    /**
     * @dev Returns balance of `owner` (this is the total balance, which includes locked tokens)
     */
    function getBalance(address _address) public view returns (uint256 value) {
        return securityTokenBalances.getBalance(_address);
    }

    /**
     * @dev Allows to update the STR's URL
     * NB : only the product's registrar can call this method
     */
    function setStrUrl(string memory _strUrl) public onlyRegistrar {
        strUrl = _strUrl;
    }

    // ISettlement
    /**
     * @dev Initiate a Subscription Settlement Transaction (counterparty buys product on primary market from the product's issuer)
     * and sends the corresponding `SubscriptionInitiated` event
     * The tokens are locked waiting for the cash leg to settle (see functions confirmPaymentReceived/confirmPaymentTransferred)
     * NB : only the product's registrar can call this method
     * NB : the call fails if the product has previously been redeemed
     */
    function initiateSubscription(
        SettlementRepositoryLibrary.PartialSettlementTransaction
            memory partialSettlementTransaction
    ) public override onlyRegistrar onlyNotRedeemedProduct {
        SettlementWorkflowLibrary.initiateSubscription(
            settlementTransactionRepository,
            securityTokenBalances,
            partialSettlementTransaction
        );
        state = ProductState.Running;
        emit SubscriptionInitiated(partialSettlementTransaction.txId);
    }

    /**
     * @dev Initiate a Trade Settlement Transaction (one counterparty buys, the other sells)
     * and sends the corresponding `TradeInitiated` event
     * The tokens are locked waiting for the cash leg to settle (see functions confirmPaymentReceived/confirmPaymentTransferred)
     * NB : only the product's registrar can call this method
     * NB : the call fails if the product has previously been redeemed
     */
    function initiateTrade(
        SettlementRepositoryLibrary.PartialSettlementTransaction
            memory partialSettlementTransaction
    ) public override onlyRegistrar onlyNotRedeemedProduct {
        SettlementWorkflowLibrary.initiateTrade(
            settlementTransactionRepository,
            securityTokenBalances,
            partialSettlementTransaction
        );
        state = ProductState.Running;
        emit TradeInitiated(partialSettlementTransaction.txId);
    }

    /**
     * @dev Execute unilateral Transfer Settlement Transactions 
     * and sends the corresponding `TransferExecuted` event
     * As there is no cash leg to settle, the tokens are transfered right away and the Settlement Transaction is marked as Settled
     * NB : only the product's registrar can call this method
     */
    function executeTransfer(
        SettlementRepositoryLibrary.PartialSettlementTransaction
            memory partialSettlementTransaction
    ) external override onlyRegistrar onlyNotRedeemedProduct{
        SettlementWorkflowLibrary.executeTransfer(
            settlementTransactionRepository,
            securityTokenBalances,
            partialSettlementTransaction
        );
        settlementTransactionRepository.setSettlementTransactionStatus(
            partialSettlementTransaction.txId,
            SettlementRepositoryLibrary.SettlementTransactionStatus.Settled
        );
        emit TransferExecuted(partialSettlementTransaction.txId);
    }

    /**
     * @dev Initiate a Redemption Settlement Transactions (issuer redeems product to all holders and gets the tokens back)
     * and sends the corresponding `RedemptionInitiated` event
     * The tokens are locked waiting for the cash leg to settle (see functions confirmPaymentReceived/confirmPaymentTransferred)
     * NB : only the product's registrar can call this method
     * NB : the call fails if the product has previously been redeemed
     * NB : extended data in the form of JSON string can be associated with this operation
     */
    function initiateRedemption(
        SettlementRepositoryLibrary.PartialSettlementTransaction[]
            memory partialSettlementTransactions,
        string memory extendedData
    ) public override onlyRegistrar onlyNotRedeemedProduct {
        uint256[] memory ids = new uint256[](
            partialSettlementTransactions.length
        );

        SettlementWorkflowLibrary.initiateRedemption(
            settlementTransactionRepository,
            securityTokenBalances,
            partialSettlementTransactions
        );

        uint256 operationId = partialSettlementTransactions[0].operationId;
        operationExtendedDataJson[operationId] = extendedData;

        for (uint256 i = 0; i < partialSettlementTransactions.length; i++) {
            ids[i] = partialSettlementTransactions[i].txId;
        }
        state = ProductState.Redeemed;
        emit RedemptionInitiated(ids);
    }

    /**
     * @dev Initiate a Coupon Settlement Transactions (issuer pays coupon to all holders)
     * and sends the corresponding `CouponInitiated` event
     * NB: the tokens are not locked for this kind of transaction
     * NB : only the product's registrar can call this method
     * NB : the call fails if the product has previously been redeemed
     * NB : extended data in the form of JSON string can be associated with this operation
     */
    function initiateCoupon(
        SettlementRepositoryLibrary.PartialSettlementTransaction[]
            memory partialSettlementTransactions,
        string memory extendedData
    ) public override onlyRegistrar onlyNotRedeemedProduct {
        uint256[] memory ids = new uint256[](
            partialSettlementTransactions.length
        );

        SettlementWorkflowLibrary.initiateCoupon(
            settlementTransactionRepository,
            partialSettlementTransactions
        );

        uint256 operationId = partialSettlementTransactions[0].operationId;
        operationExtendedDataJson[operationId] = extendedData;

        for (uint256 i = 0; i < partialSettlementTransactions.length; i++) {
            ids[i] = partialSettlementTransactions[i].txId;
        }
        emit CouponInitiated(ids);
    }

    /**
     * @dev Confirms that the payment for Settlement Transaction with id `_settlementTransactionId` was received
     * and sends the corresponding `PaymentReceived` event
     * The tokens are actually transferred to the recipient (except for Coupon where no token is exchanged)
     * If the Settlement Transaction is part of a Redemption operation, the tokens are burnt right after being transferred back to the issuer.
     * The Settlement Transaction state moves to Processed
     * The call fails if the Settlement Transaction with id `_settlementTransactionId` is not found in the repository
     * The call fails if the Settlement Transaction is not in the Acknowledged state.
     * NB : only the product's settlement agent can call this method
     */
    function confirmPaymentReceived(uint256 _settlementTransactionId)
        external
        override
        settlerOnly
    {
        SettlementRepositoryLibrary.SettlementTransaction
            memory settlementTransaction = settlementTransactionRepository
                .getSettlementTransactionById(_settlementTransactionId);
        if (
            settlementTransaction.operationType ==
            SettlementRepositoryLibrary.OperationType.Undefined
        ) revert InvalidOperationType();
        _handleConfirmPaymentReceived(_settlementTransactionId);
    }

    /**
     * @dev Internal method that does the actual job of `confirmPaymentReceived`
     */
    function _handleConfirmPaymentReceived(uint256 settlementTransactionId)
        internal
    {
        SettlementRepositoryLibrary.SettlementTransaction
            memory st = settlementTransactionRepository
                .getSettlementTransactionById(settlementTransactionId);

        if (
            st.status !=
            SettlementRepositoryLibrary.SettlementTransactionStatus.Acknowledged
        ) revert SettlementTransactionNotInAcknowledgedState();

        if (
            st.operationType != SettlementRepositoryLibrary.OperationType.Coupon
        ) {
            securityTokenBalances.transferLocked(
                st.deliverySenderAccountNumber,
                st.deliveryReceiverAccountNumber,
                st.deliveryQuantity
            );
        }

        if (
            st.operationType ==
            SettlementRepositoryLibrary.OperationType.Redemption
        ) {
            internalBurn(
                st.deliveryReceiverAccountNumber,
                st.deliveryQuantity
            );
        }

        settlementTransactionRepository.setSettlementTransactionStatus(
            settlementTransactionId,
            SettlementRepositoryLibrary.SettlementTransactionStatus.Processed
        );
        emit PaymentReceived(settlementTransactionId, st.operationType);
    }

    /**
     * @dev Confirms that the payment for Settlement Transaction with id `_settlementTransactionId` was transferred to the recipient
     * and sends the corresponding `PaymentTransferred` event
     * The Settlement Transaction state moves to Settled
     * The call fails if the Settlement Transaction with id `_settlementTransactionId` is not found in the repository
     * The call fails if the Settlement Transaction is not in the Processed state.
     * NB : only the product's settlement agent can call this method
     */
    function confirmPaymentTransferred(uint256 _settlementTransactionId)
        external
        override
        settlerOnly
    {
        _handleConfirmPaymentTransferred(_settlementTransactionId);
    }

    /**
     * @dev Internal method that does the actual job of `confirmPaymentTransferred`
     */
    function _handleConfirmPaymentTransferred(uint256 _settlementTransactionId)
        internal
    {
        SettlementRepositoryLibrary.SettlementTransaction
            memory st = settlementTransactionRepository
                .getSettlementTransactionById(_settlementTransactionId);
        if (
            st.operationType ==
            SettlementRepositoryLibrary.OperationType.Undefined
        ) revert InvalidOperationType();
        if (
            st.status !=
            SettlementRepositoryLibrary.SettlementTransactionStatus.Processed
        ) revert SettlementTransactionNotInProcessedState();

        settlementTransactionRepository.setSettlementTransactionStatus(
            _settlementTransactionId,
            SettlementRepositoryLibrary.SettlementTransactionStatus.Settled
        );

        emit PaymentTransferred(_settlementTransactionId, st.operationType);
    }

    
    /**
     * @dev Returns the details of Settlement Transaction with id `_settlementTransactionId`
     */
    function getSettlementTransaction(uint256 _settlementTransactionId)
        external
        view
        returns (SettlementRepositoryLibrary.SettlementTransaction memory)
    {
        return
            settlementTransactionRepository.getSettlementTransactionById(
                _settlementTransactionId
            );
    }

    /**
     * @dev Cancels ongoing settlement transaction with id `settlementTransactionId`
     * and sends the corresponding `SettlementTransactionCancelled` event
     * Unlocks tokens
     * Fails if the settlement transaction is not in the Acknowledged state.
     * NB : only the product's registrar can call this method     *  
     */
    function cancelSettlementTransaction(uint256 settlementTransactionId)
        public
        override
        onlyRegistrar
    {
        SettlementWorkflowLibrary.cancelSettlementTransaction(
            settlementTransactionRepository,
            securityTokenBalances,
            settlementTransactionId
        );

        emit SettlementTransactionCancelled(settlementTransactionId);
    }

    /**
     * @dev Internal function that recomputes the totalSupply when it changes (mint/burn)
     * 
     */
    function recomputeTotalSupply() internal {
        seriesOutstandingNominalAmountCent =
            seriesNominalAmountCent -
            seriesRedeemedAmountCent;
        totalSupply = seriesOutstandingNominalAmountCent / denominationCent;
    }

    /**
     * @dev Internal function that adds a new tranche and updates aggregated amounts accordingly.
     * 
     */
    function internalCreateTranche(Tranche memory newTranche) internal {
        tranches.push(newTranche);
        seriesNominalAmountCent += newTranche.nominalAmountCent;
        recomputeTotalSupply();
    }

    /**
     * @dev Issue a new tranche of tokens for the same product
     * Mints the corresponding number of tokens
     * The product's totalSupply is updated accordingly.
     * NB : only the product's registrar can call this method
     */
    function createTranche(Tranche memory newTranche) public onlyRegistrar {
        securityTokenBalances.mint(issuer.account, newTranche.nominalAmountCent / denominationCent);
        internalCreateTranche(newTranche);
    }
}

// File: contracts/registry/interfaces/IProductRegistry.sol



pragma solidity 0.8.17;

/**
 * @title The product registry
 * @dev The registry listing all financial products handled by a given registrar(the owner of the registry).
 */
interface IProductRegistry {
    /** 
     * @dev Add a new product to the registry
     * @param name The name of the product
     * @param isinCode The ISIN code of the product
     * @param productAddress The adress of the product's contract
     */
    function listProduct(
        string calldata name,
        string calldata isinCode,
        address productAddress
    ) external;

    /** 
     * @dev Get the address of a product by providing its name
     * @param name The name of the product
     * @return product The found product's contract address
     */
    function getProductByName(string calldata name)
        external
        view
        returns (address product);

    /**
     * @dev Get the address of a product by providing its ISIN code
     * @param isin The ISIN code of the product
     * @return product The found product's contract address
     */
    function getProductByIsinCode(string calldata isin)
        external
        view
        returns (address product);

    /**
     * @dev Get all products' addresses at once
     * @return product An array of all producs' addresses
     */
    function getAllProducts() external view returns (address[] memory product);

    /// @dev Remove a product from the registry
    /// @param isin The ISIN code of the product
    function unListProduct(string calldata isin) external;
}

// File: contracts/utils/Ownable.sol



pragma solidity 0.8.17;

/**
 * @dev Used when a function reserved to the registrar is called by some other account
 */
import { UnauthorizedRegistrar, ZeroAddressCheck } from "./ProductErrorReporter.sol";

/**
 * @dev The Ownable contract handles an owner, called the registrar, and a modifier used to protect methods reserved to the registrar.
 */
abstract contract Ownable {
    address private registrarAgent;

    /**
     * @dev `_registrar` will be the owner of the contract
     */
    constructor(address _registrar) {
        if(_registrar == address(0))
            revert ZeroAddressCheck();
        registrarAgent = _registrar;
    }
    /**
     * @dev Throws an error if the caller is not the contract's owner(a.k.a. registrar)
     */
    modifier onlyRegistrar() {
        if (msg.sender != registrarAgent) revert UnauthorizedRegistrar();
        _;
    }
}

// File: contracts/utils/ProductErrorReporter.sol



pragma solidity 0.8.17;

error TotalSupplyNotMatchTotalInitialSupply();

error PayingAgentNotSet();
error GuarantorNotSet();

error UnauthorizedIssuer();
error UnauthorizedSettler();

error ProductIsFullyRedeemed();

error SettlementTransactionNotInProcessedState();
error SettlementTransactionNotInAcknowledgedState();
error SettlementTransactionAlreadyExists();
error InvalidSettlementTransactionStatus();
error DeliveryReceiverAccountNumberNotMatchTokenIssuer();

error InvalidOperationType();

error InvalidTrustedToken();
error NotSupportedTrustedToken();

error InsufficientDisposableBalance();
error InsufficientBalance();
error InsufficientLockedBalance();

error TransferRequestNotFound();
error InvalidTransferRequestStatus();

error ZeroAddressCheck();
error OwnerHasOngoingApprove();
error ApproveDoesNotExists();
error InvalidApproveStatus();
error InsufficientAllowance();
error UnauthorizedRegistrar();

error ZeroValueCheck();
error InvalidReceiver();