CPTOKEN_V3

Description:

Multi-signature wallet contract requiring multiple confirmations for transaction execution.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "src/contracts/upgradeable_token/cptoken/CPTOKEN_V3.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;

/// @title  DCP contract
/// @author Zeconomy
/// @notice This contract is of ERC1155 token standard


import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import { ERC1155Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC1155/ERC1155Upgradeable.sol";
import { PROGRAM } from  "../../program/PROGRAM.sol";
import { PROGRAM_V3 } from  "../../program/PROGRAM_V3.sol"; 
import { Enums, Helper, Structs } from "../../utils/Utils.sol";
import { Events } from "../../utils/Events.sol";
import { DomainSeparator } from "../../utils/DomainSeparator.sol";
import { ZEC_EIP712 } from "../../utils/ZEC_EIP712.sol";
import { GeneralReverts, ProgramReverts } from "../../utils/Reverts.sol";

/// @custom:oz-upgrades-from CPTOKEN_V2
contract CPTOKEN_V3 is Initializable, ERC1155Upgradeable, DomainSeparator {

    using Helper for uint256;
    using Helper for uint32;
    using Helper for address;
    using ZEC_EIP712 for bytes; 
    
    string private _name;             
    string private _symbol;           
    address private _contractOwner;
    address private _mhtlc;
    uint256 private _signNonce;
    PROGRAM private _DCP_PROGRAM;
    
    mapping(uint256 => uint256) private _totalSupply;
    mapping(uint256 => mapping(address => Structs.LockedTokens)) private _lockedTokens; // map issuance id to the addresses to be locked
    
    mapping(uint256 => bool) private _paymentComplete;
    mapping(address => bool) private _signers;
    PROGRAM_V3 private _DCP_PROGRAM_V3;


    /// @dev    modifier that requires that the sender must be the contract owner
    modifier onlyContractOwner {
        require(
            msg.sender == _contractOwner,
            "Only contractOwner can call this function."
        );
        _;
    }

    
    /**
        initializer to initialize in place of the constructor; the token name, token symbol, the contract owner and the token uri 

        @param _tokenName is the name assigned to the token on deployment
        @param _tokenSymbol is the symbol assigned to the token on deployment
        @param _uri is the token URI assigned on deployment

        @dev    the initialize function is used in place of the constructor to ensure that the contract is upgradable
    */

    function initialize(
        string memory _tokenName, 
        string memory _tokenSymbol, 
        string memory _uri, 
        string calldata _eip712Name, 
        string calldata _eip712Version,  
        address _program, 
        address _aSigner
    )
        public 
        virtual 
        initializer
    {
        
        _contractOwner = msg.sender;         //  set contract owner
        _name = _tokenName;                  //  set token name
        _symbol = _tokenSymbol;              //  set token symbol
        __ERC1155_init(_uri);               //  set token uri  ( upgradable safe )
        _setDomainSeparator(_eip712Name, _eip712Version);
        _signers[_aSigner] = true;

    }

    function programV2Init() public virtual reinitializer(2) {
        _DCP_PROGRAM_V3 = PROGRAM_V3(address(_DCP_PROGRAM));
    }

    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor() {
        _disableInitializers();
    }

    function name() public view returns (string memory) {
        return _name;
    }

    function symbol() public view returns (string memory) {
        return _symbol;
    }

    /**
        @dev    function to get the address of the contract owner
        @return contractOwner which is the address of the contract owner
    */

    function getContractOwner() public view returns (address) {
        return _contractOwner;
    }


   
    /// @notice issue tokens
    /// @notice issuance must be on or after program effective date
    /// @notice issuance must be before program  expiration
    
    function issueToken(
        address account, 
        uint256 tokenId, 
        uint256 amount,
        uint256 amountToLock,  
        address toBeneficiary, 
        string calldata notes,
        uint256 programId
    ) external { 

        tokenId.isZero();
        amount.isZero();

        /// @notice: Initialize token if not initialized
        if (!_DCP_PROGRAM_V3.isTokenInitialized(tokenId)) {
            _DCP_PROGRAM_V3.initializeToken(tokenId, programId);
        }

        uint256 _programId = _DCP_PROGRAM.getTokenProgram(tokenId)._programId;
        
        _DCP_PROGRAM.revert__If__deauthorized(_programId);
        account.revert__If__Agent(_mhtlc, _programId, _DCP_PROGRAM);
        account.revert__If__Address__Zero();
        _programId.validateEffectiveDateAndExpiration(_DCP_PROGRAM);

        _revert__If__Settled(tokenId);

        if (amountToLock > amount)
            revert GeneralReverts.InvalidAmountToLock(amountToLock);

        if(
            msg.sender.isIssuerForProgram(_programId, _DCP_PROGRAM) || 
            msg.sender == _mhtlc ||
            msg.sender.isDepositoryAgentForProgram(_programId, _DCP_PROGRAM)
        ) {

            /// @dev    handle token lock

            if (amountToLock != 0) {

                if (_lockedTokens[tokenId][account]._isLocked == false)
                    _lockedTokens[tokenId][account] = Structs.LockedTokens(true, amountToLock);
                
                else if (_lockedTokens[tokenId][account]._isLocked == true) 
                    _lockedTokens[tokenId][account]._amountLocked += amountToLock;

            } 
            _totalSupply[tokenId] += amount;                  
            _mint(account, tokenId, amount, "");
        
        }

        else {
            msg.sender.throwCallerError();
        }           
        
    }

    function issueToken(
        address account, 
        uint256 tokenId, 
        uint256 amount,
        uint256 amountToLock,  
        address toBeneficiary, 
        string calldata notes,
        uint256 programId,
        uint256 zenocoin
    ) external virtual { 

        tokenId.isZero();
        amount.isZero();

        /// @notice: Initialize token if not initialized
        if (!_DCP_PROGRAM_V3.isTokenInitialized(tokenId)) {
            _DCP_PROGRAM_V3.initializeToken(tokenId, programId);
        }

        uint256 _programId = _DCP_PROGRAM.getTokenProgram(tokenId)._programId;
        
        _DCP_PROGRAM.revert__If__deauthorized(_programId);
        account.revert__If__Agent(_mhtlc, _programId, _DCP_PROGRAM);
        account.revert__If__Address__Zero();
        _programId.validateEffectiveDateAndExpiration(_DCP_PROGRAM);

        _revert__If__Settled(tokenId);

        if (amountToLock > amount)
            revert GeneralReverts.InvalidAmountToLock(amountToLock);

        if(
            msg.sender.isIssuerForProgram(_programId, _DCP_PROGRAM) || 
            msg.sender == _mhtlc ||
            msg.sender.isDepositoryAgentForProgram(_programId, _DCP_PROGRAM)
        ) {

            /// @dev    handle token lock

            if (amountToLock != 0) {

                if (_lockedTokens[tokenId][account]._isLocked == false)
                    _lockedTokens[tokenId][account] = Structs.LockedTokens(true, amountToLock);
                
                else if (_lockedTokens[tokenId][account]._isLocked == true) 
                    _lockedTokens[tokenId][account]._amountLocked += amountToLock;

            } 
            _totalSupply[tokenId] += amount;
            // __totalSupply += amount;                   
            _mint(account, tokenId, amount, "");
        
        }

        else {
            msg.sender.throwCallerError();
        }           
        
    }
  
    /// @notice function to burn the token can only be called by the holder
    function burnToken(uint256 tokenId, uint256 amount) external  {

        if ( amount > balanceOf(msg.sender, tokenId))
            revert GeneralReverts.InsufficientBalance();

        _revert__If__Settled(tokenId);
        _totalSupply[tokenId] -= amount;
        _burn(msg.sender,tokenId,amount);
        
    }
    
    /**
        * @dev Total supply of issuance id
     */
    function totalSupply(uint256 tokenID) public view returns (uint256) {
        return _totalSupply[tokenID];
    }

    /**
     * @dev Indicates weither any token exist with a given id, or not.
     */
    function exists(uint256 tokenID) public view returns (bool) {
        return totalSupply(tokenID) > 0;
    }
    
        /**
     * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {safeTransferFrom}.
     *
     * Emits a {TransferBatch} event.
     *
     * Requirements:
     *
     * - `ids` and `amounts` must have the same length.
     * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the
     * acceptance magic value.
     */
    function safeBatchTransferFrom(
        address from,
        address to,
        uint256[] memory tokenIds,
        uint256[] memory amounts,
        bytes memory data
    ) 
        public
        virtual
        override
    {

        require(_validateBatchPayment(tokenIds, from) == false, "Error: Payment Completed");
        to.revert__If__Agent(_mhtlc, tokenIds, _DCP_PROGRAM);

        if (to != _mhtlc)
            address(this).revert__If__Locked(from, tokenIds, amounts);

        require(from.isWhitelistedForBatch(tokenIds, _DCP_PROGRAM) == true, "Error: not whitelisted");
        require(to.isWhitelistedForBatch(tokenIds, _DCP_PROGRAM) == true, "Error: not whitelisted");
        require(from == _msgSender() || isApprovedForAll(from, _msgSender()),"ERC1155: caller is not owner nor approved" );
        _safeBatchTransferFrom(from, to, tokenIds, amounts, data);
        
    }

    /// @dev Safe transfer with beneficiaries
    function safeBatchTransferFrom(
        address from,
        address to,
        uint256[] memory tokenIds,
        uint256[] memory amounts,
        bytes memory data,
        address fromBeneficiary,
        address toBeneficiary,
        string memory note
    ) 
        external 
        virtual
    
    {
        require(_validateBatchPayment(tokenIds, from) == false, "Error: Payment Completed");
        to.revert__If__Agent(_mhtlc, tokenIds, _DCP_PROGRAM);

        if (to != _mhtlc)
            address(this).revert__If__Locked(from, tokenIds, amounts);
            
        from.validateBeneficiaryForBatch(fromBeneficiary, tokenIds, _DCP_PROGRAM);
        to.validateBeneficiaryForBatch(toBeneficiary, tokenIds, _DCP_PROGRAM);
        require(from == _msgSender() || isApprovedForAll(from, _msgSender()),"ERC1155: caller is not owner nor approved" );
        _safeBatchTransferFrom(from, to, tokenIds, amounts, data);
    }
    
    /**
        * @dev Transfers `amount` tokens of token type `id` from `from` to `to`.
        *
        * Emits a {TransferSingle} event.
        *
        * Requirements:
        *
        * - `to` cannot be the zero address.
        * - If the caller is not `from`, it must be have been approved to spend ``from``'s tokens via {setApprovalForAll}.
        * - `from` must have a balance of tokens of type `id` of at least `amount`.
        * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the
        * acceptance magic value.
    */
     
    function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId,
        uint256 amount,
        bytes memory data
    ) 
        public
        virtual 
        override
    {
        
        _revert__If__Settled(tokenId);
        uint256 _programId = _DCP_PROGRAM.getTokenProgram(tokenId)._programId;
        to.revert__If__Agent(_mhtlc, _programId, _DCP_PROGRAM);

        if (to != _mhtlc)
            address(this).revert__If__Locked(from, tokenId, amount);

        require(from.isWhitelistedForSingle(_programId, _DCP_PROGRAM) == true, "Error: not whitelisted");
        require(to.isWhitelistedForSingle(_programId, _DCP_PROGRAM) == true, "Error: not whitelisted");
        require(msg.sender == from || isApprovedForAll(from, _msgSender()),"ERC1155: caller is not owner nor approved");
        _safeTransferFrom(from, to, tokenId, amount, data);
    }
    
    /// @dev    Safe transfer with beneficiaries
    function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId,
        uint256 amount,
        bytes memory data,
        address fromBeneficiary,
        address toBeneficiary,
        string calldata note
    ) 
        external 
        virtual 
    {

        _revert__If__Settled(tokenId);
        uint256 _programId = _DCP_PROGRAM.getTokenProgram(tokenId)._programId;
        to.revert__If__Agent(_mhtlc, _programId, _DCP_PROGRAM);

        if (to != _mhtlc)
            address(this).revert__If__Locked(from, tokenId, amount);

        from.validateBeneficiaryForSingle(fromBeneficiary, _programId, _DCP_PROGRAM);
        to.validateBeneficiaryForSingle(toBeneficiary, _programId, _DCP_PROGRAM);
        require(msg.sender == from || isApprovedForAll(from, _msgSender()),"ERC1155: caller is not owner nor approved");
        _safeTransferFrom(from, to, tokenId, amount, data);


    }

    /**
        @dev    function to transfer token ownership
        @notice address must not be address 0
        @notice address must not be the current contract owner
     */
    
    function transferContractOwnership(address _address, bytes memory _signature, ZEC_EIP712.AdministratorAuth memory _administratorAuth) public onlyContractOwner {
        
        if (_address == address(0))
            revert GeneralReverts.AddressZero();

        require(_address != _contractOwner, "Cannot reassign to current owner");

        if (_administratorAuth.timeSigned > block.timestamp)
            revert GeneralReverts.InvalidSignatureTime();
       
        if ((_administratorAuth.timeSigned + 4 hours) < block.timestamp)
            revert GeneralReverts.SignatureExpired();

        if (_administratorAuth.nonce != _signNonce)
            revert GeneralReverts.InvalidNonce();

        if (_signature.length != 65)
            revert GeneralReverts.InvalidSignature();

        address _recoveredSigner = _signature.verifyAdministratorSignature(_EIP712Domain, _administratorAuth);
        
        if ( _signers[_recoveredSigner] != true )
            revert GeneralReverts.InvalidSigner(_recoveredSigner); 
        
        ++ _signNonce; // update nonce    
        _contractOwner = _address;
    }

    function nonce() external view returns (uint256) {
        return _signNonce;
    }

    /**
        @dev    To lock an holder's issuance
     */
    function authorizeLock(uint256 _tokenId, uint256 _amount, address _holder) external returns (bool success) {

        if (
            !msg.sender.isIssuerForIssuance(_tokenId, _DCP_PROGRAM) &&
            !msg.sender.isDepositoryAgentForIssuance(_tokenId, _DCP_PROGRAM)
            )
        msg.sender.throwCallerError();
        
        _lockedTokens[_tokenId][_holder] = Structs.LockedTokens(true, _amount);
        return true;
    }

    /**
        @dev    To unlock an holder's issuance
    */
    function authorizeUnlock(uint256 _tokenId, uint256 _amount, address _holder) external returns (bool success) {
         
        if (
            !msg.sender.isIssuerForIssuance(_tokenId, _DCP_PROGRAM) &&
            !msg.sender.isDepositoryAgentForIssuance(_tokenId, _DCP_PROGRAM)
            )
        msg.sender.throwCallerError();

        uint256 _amountLocked = _lockedTokens[_tokenId][_holder]._amountLocked;

        if (_amount < _amountLocked)
            _lockedTokens[_tokenId][_holder]._amountLocked = _amountLocked - _amount;
        
        else if (_amount == _amountLocked)
            _lockedTokens[_tokenId][_holder] = Structs.LockedTokens(false, 0);
        
        else 
            revert GeneralReverts.AmountGreaterThanLockedAmount(_amountLocked);
        return true;

    }
    
  
    /**
        @dev function to set the addresses for the mint-to-pay htlc
     */

    function setHtlc(address _mintToPayHtlc) external onlyContractOwner {
        _mintToPayHtlc.revert__If__Address__Zero();
        _mhtlc = _mintToPayHtlc;
    }
    
    /// @dev    validate batch payment-complete
    function _validateBatchPayment(uint256[] memory _ids, address _account) internal view returns (bool _pass)  {

        uint256 _length = _ids.length;

        for (uint256 index = 0; index < _length; ++ index) {

            if(_paymentComplete[_ids[index]] == true) {
                return true;
            }
        }
        return false;

    }
  
    /// @dev    validate batch payment-complete
    /// @dev    for external calls only, to fetch the list of settled issuance ids
    
    function validateBatchPayment(uint256[] calldata _ids) external view returns (bool[] memory _settled)  {

        bool[] memory settled = new bool[](_ids.length);
        for (uint256 index = 0; index < _ids.length; index++) {

            bool _isSettled = _paymentComplete[_ids[index]];
            settled[index] = _isSettled;
        }

        return settled;

    }

    /// @notice Mark an issuance id as paid for an holder
    /// @notice Caller must be the valid agent
    /// @notice Issuance id must be unpaid bgefore the call
    /// @notice Holder must be whitelisted under the program linked to the issuance id
    /// @notice tokens marked as paid are removed from max total supply

    function markPaid(uint256 _tokenId) external  {

        uint256 _programId = _DCP_PROGRAM.getTokenProgram(_tokenId)._programId;

        msg.sender.validatePaymentAgent(
            _programId,
            Helper.CallerValidationType.PROGRAM,
            _DCP_PROGRAM
        );
        
        if (_paymentComplete[_tokenId] == true)
            revert GeneralReverts.PaymentCompleted(_tokenId);
        
        _paymentComplete[_tokenId] = true;
    }

    /// @notice Check if an issuance id has been marked as paid for a particular holder
    /// @return settled which is the payment status
    function isPaid(uint256 _tokenId) public view returns (bool settled) {
        return _paymentComplete[_tokenId];
    }

    /// @dev    revert if an issuance has been marked as paid
    function _revert__If__Settled(uint256 _issuanceId) internal {
        
        if (isPaid(_issuanceId) == true)
            revert GeneralReverts.PaymentCompleted(_issuanceId);

    }

    /// @dev    To set domain separator
    function setDomainSeparator(
        string calldata _eip712Name,
        string calldata _eip712Version
    )
        external
    {
        require(msg.sender == _contractOwner, "Only contract owner");
        _setDomainSeparator(_eip712Name, _eip712Version);
    }

    /// @dev    To fetch locked tokens
    function getLockedTokens(address _holder, uint256 _tokenId) external view returns (Structs.LockedTokens memory) {
        return _lockedTokens[_tokenId][_holder];
    }
    
}

"
    },
    "lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (proxy/utils/Initializable.sol)

pragma solidity ^0.8.20;

/**
 * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
 * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
 * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
 * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
 *
 * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
 * reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
 * case an upgrade adds a module that needs to be initialized.
 *
 * For example:
 *
 * [.hljs-theme-light.nopadding]
 * ```solidity
 * contract MyToken is ERC20Upgradeable {
 *     function initialize() initializer public {
 *         __ERC20_init("MyToken", "MTK");
 *     }
 * }
 *
 * contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
 *     function initializeV2() reinitializer(2) public {
 *         __ERC20Permit_init("MyToken");
 *     }
 * }
 * ```
 *
 * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
 * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
 *
 * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
 * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
 *
 * [CAUTION]
 * ====
 * Avoid leaving a contract uninitialized.
 *
 * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
 * contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
 * the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
 *
 * [.hljs-theme-light.nopadding]
 * ```
 * /// @custom:oz-upgrades-unsafe-allow constructor
 * constructor() {
 *     _disableInitializers();
 * }
 * ```
 * ====
 */
abstract contract Initializable {
    /**
     * @dev Storage of the initializable contract.
     *
     * It's implemented on a custom ERC-7201 namespace to reduce the risk of storage collisions
     * when using with upgradeable contracts.
     *
     * @custom:storage-location erc7201:openzeppelin.storage.Initializable
     */
    struct InitializableStorage {
        /**
         * @dev Indicates that the contract has been initialized.
         */
        uint64 _initialized;
        /**
         * @dev Indicates that the contract is in the process of being initialized.
         */
        bool _initializing;
    }

    // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Initializable")) - 1)) & ~bytes32(uint256(0xff))
    bytes32 private constant INITIALIZABLE_STORAGE = 0xf0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00;

    /**
     * @dev The contract is already initialized.
     */
    error InvalidInitialization();

    /**
     * @dev The contract is not initializing.
     */
    error NotInitializing();

    /**
     * @dev Triggered when the contract has been initialized or reinitialized.
     */
    event Initialized(uint64 version);

    /**
     * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
     * `onlyInitializing` functions can be used to initialize parent contracts.
     *
     * Similar to `reinitializer(1)`, except that in the context of a constructor an `initializer` may be invoked any
     * number of times. This behavior in the constructor can be useful during testing and is not expected to be used in
     * production.
     *
     * Emits an {Initialized} event.
     */
    modifier initializer() {
        // solhint-disable-next-line var-name-mixedcase
        InitializableStorage storage $ = _getInitializableStorage();

        // Cache values to avoid duplicated sloads
        bool isTopLevelCall = !$._initializing;
        uint64 initialized = $._initialized;

        // Allowed calls:
        // - initialSetup: the contract is not in the initializing state and no previous version was
        //                 initialized
        // - construction: the contract is initialized at version 1 (no reininitialization) and the
        //                 current contract is just being deployed
        bool initialSetup = initialized == 0 && isTopLevelCall;
        bool construction = initialized == 1 && address(this).code.length == 0;

        if (!initialSetup && !construction) {
            revert InvalidInitialization();
        }
        $._initialized = 1;
        if (isTopLevelCall) {
            $._initializing = true;
        }
        _;
        if (isTopLevelCall) {
            $._initializing = false;
            emit Initialized(1);
        }
    }

    /**
     * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
     * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
     * used to initialize parent contracts.
     *
     * A reinitializer may be used after the original initialization step. This is essential to configure modules that
     * are added through upgrades and that require initialization.
     *
     * When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`
     * cannot be nested. If one is invoked in the context of another, execution will revert.
     *
     * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
     * a contract, executing them in the right order is up to the developer or operator.
     *
     * WARNING: Setting the version to 2**64 - 1 will prevent any future reinitialization.
     *
     * Emits an {Initialized} event.
     */
    modifier reinitializer(uint64 version) {
        // solhint-disable-next-line var-name-mixedcase
        InitializableStorage storage $ = _getInitializableStorage();

        if ($._initializing || $._initialized >= version) {
            revert InvalidInitialization();
        }
        $._initialized = version;
        $._initializing = true;
        _;
        $._initializing = false;
        emit Initialized(version);
    }

    /**
     * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
     * {initializer} and {reinitializer} modifiers, directly or indirectly.
     */
    modifier onlyInitializing() {
        _checkInitializing();
        _;
    }

    /**
     * @dev Reverts if the contract is not in an initializing state. See {onlyInitializing}.
     */
    function _checkInitializing() internal view virtual {
        if (!_isInitializing()) {
            revert NotInitializing();
        }
    }

    /**
     * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
     * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
     * to any version. It is recommended to use this to lock implementation contracts that are designed to be called
     * through proxies.
     *
     * Emits an {Initialized} event the first time it is successfully executed.
     */
    function _disableInitializers() internal virtual {
        // solhint-disable-next-line var-name-mixedcase
        InitializableStorage storage $ = _getInitializableStorage();

        if ($._initializing) {
            revert InvalidInitialization();
        }
        if ($._initialized != type(uint64).max) {
            $._initialized = type(uint64).max;
            emit Initialized(type(uint64).max);
        }
    }

    /**
     * @dev Returns the highest version that has been initialized. See {reinitializer}.
     */
    function _getInitializedVersion() internal view returns (uint64) {
        return _getInitializableStorage()._initialized;
    }

    /**
     * @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.
     */
    function _isInitializing() internal view returns (bool) {
        return _getInitializableStorage()._initializing;
    }

    /**
     * @dev Returns a pointer to the storage namespace.
     */
    // solhint-disable-next-line var-name-mixedcase
    function _getInitializableStorage() private pure returns (InitializableStorage storage $) {
        assembly {
            $.slot := INITIALIZABLE_STORAGE
        }
    }
}
"
    },
    "lib/openzeppelin-contracts-upgradeable/contracts/token/ERC1155/ERC1155Upgradeable.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC1155/ERC1155.sol)

pragma solidity ^0.8.20;

import {IERC1155} from "@openzeppelin/contracts/token/ERC1155/IERC1155.sol";
import {IERC1155Receiver} from "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol";
import {IERC1155MetadataURI} from "@openzeppelin/contracts/token/ERC1155/extensions/IERC1155MetadataURI.sol";
import {ContextUpgradeable} from "../../utils/ContextUpgradeable.sol";
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import {ERC165Upgradeable} from "../../utils/introspection/ERC165Upgradeable.sol";
import {Arrays} from "@openzeppelin/contracts/utils/Arrays.sol";
import {IERC1155Errors} from "@openzeppelin/contracts/interfaces/draft-IERC6093.sol";
import {Initializable} from "../../proxy/utils/Initializable.sol";

/**
 * @dev Implementation of the basic standard multi-token.
 * See https://eips.ethereum.org/EIPS/eip-1155
 * Originally based on code by Enjin: https://github.com/enjin/erc-1155
 */
abstract contract ERC1155Upgradeable is Initializable, ContextUpgradeable, ERC165Upgradeable, IERC1155, IERC1155MetadataURI, IERC1155Errors {
    using Arrays for uint256[];
    using Arrays for address[];

    /// @custom:storage-location erc7201:openzeppelin.storage.ERC1155
    struct ERC1155Storage {
        mapping(uint256 id => mapping(address account => uint256)) _balances;

        mapping(address account => mapping(address operator => bool)) _operatorApprovals;

        // Used as the URI for all token types by relying on ID substitution, e.g. https://token-cdn-domain/{id}.json
        string _uri;
    }

    // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.ERC1155")) - 1)) & ~bytes32(uint256(0xff))
    bytes32 private constant ERC1155StorageLocation = 0x88be536d5240c274a3b1d3a1be54482fd9caa294f08c62a7cde569f49a3c4500;

    function _getERC1155Storage() private pure returns (ERC1155Storage storage $) {
        assembly {
            $.slot := ERC1155StorageLocation
        }
    }

    /**
     * @dev See {_setURI}.
     */
    function __ERC1155_init(string memory uri_) internal onlyInitializing {
        __ERC1155_init_unchained(uri_);
    }

    function __ERC1155_init_unchained(string memory uri_) internal onlyInitializing {
        _setURI(uri_);
    }

    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165Upgradeable, IERC165) returns (bool) {
        return
            interfaceId == type(IERC1155).interfaceId ||
            interfaceId == type(IERC1155MetadataURI).interfaceId ||
            super.supportsInterface(interfaceId);
    }

    /**
     * @dev See {IERC1155MetadataURI-uri}.
     *
     * This implementation returns the same URI for *all* token types. It relies
     * on the token type ID substitution mechanism
     * https://eips.ethereum.org/EIPS/eip-1155#metadata[defined in the EIP].
     *
     * Clients calling this function must replace the `\{id\}` substring with the
     * actual token type ID.
     */
    function uri(uint256 /* id */) public view virtual returns (string memory) {
        ERC1155Storage storage $ = _getERC1155Storage();
        return $._uri;
    }

    /**
     * @dev See {IERC1155-balanceOf}.
     */
    function balanceOf(address account, uint256 id) public view virtual returns (uint256) {
        ERC1155Storage storage $ = _getERC1155Storage();
        return $._balances[id][account];
    }

    /**
     * @dev See {IERC1155-balanceOfBatch}.
     *
     * Requirements:
     *
     * - `accounts` and `ids` must have the same length.
     */
    function balanceOfBatch(
        address[] memory accounts,
        uint256[] memory ids
    ) public view virtual returns (uint256[] memory) {
        if (accounts.length != ids.length) {
            revert ERC1155InvalidArrayLength(ids.length, accounts.length);
        }

        uint256[] memory batchBalances = new uint256[](accounts.length);

        for (uint256 i = 0; i < accounts.length; ++i) {
            batchBalances[i] = balanceOf(accounts.unsafeMemoryAccess(i), ids.unsafeMemoryAccess(i));
        }

        return batchBalances;
    }

    /**
     * @dev See {IERC1155-setApprovalForAll}.
     */
    function setApprovalForAll(address operator, bool approved) public virtual {
        _setApprovalForAll(_msgSender(), operator, approved);
    }

    /**
     * @dev See {IERC1155-isApprovedForAll}.
     */
    function isApprovedForAll(address account, address operator) public view virtual returns (bool) {
        ERC1155Storage storage $ = _getERC1155Storage();
        return $._operatorApprovals[account][operator];
    }

    /**
     * @dev See {IERC1155-safeTransferFrom}.
     */
    function safeTransferFrom(address from, address to, uint256 id, uint256 value, bytes memory data) public virtual {
        address sender = _msgSender();
        if (from != sender && !isApprovedForAll(from, sender)) {
            revert ERC1155MissingApprovalForAll(sender, from);
        }
        _safeTransferFrom(from, to, id, value, data);
    }

    /**
     * @dev See {IERC1155-safeBatchTransferFrom}.
     */
    function safeBatchTransferFrom(
        address from,
        address to,
        uint256[] memory ids,
        uint256[] memory values,
        bytes memory data
    ) public virtual {
        address sender = _msgSender();
        if (from != sender && !isApprovedForAll(from, sender)) {
            revert ERC1155MissingApprovalForAll(sender, from);
        }
        _safeBatchTransferFrom(from, to, ids, values, data);
    }

    /**
     * @dev Transfers a `value` amount of tokens of type `id` from `from` to `to`. Will mint (or burn) if `from`
     * (or `to`) is the zero address.
     *
     * Emits a {TransferSingle} event if the arrays contain one element, and {TransferBatch} otherwise.
     *
     * Requirements:
     *
     * - If `to` refers to a smart contract, it must implement either {IERC1155Receiver-onERC1155Received}
     *   or {IERC1155Receiver-onERC1155BatchReceived} and return the acceptance magic value.
     * - `ids` and `values` must have the same length.
     *
     * NOTE: The ERC-1155 acceptance check is not performed in this function. See {_updateWithAcceptanceCheck} instead.
     */
    function _update(address from, address to, uint256[] memory ids, uint256[] memory values) internal virtual {
        ERC1155Storage storage $ = _getERC1155Storage();
        if (ids.length != values.length) {
            revert ERC1155InvalidArrayLength(ids.length, values.length);
        }

        address operator = _msgSender();

        for (uint256 i = 0; i < ids.length; ++i) {
            uint256 id = ids.unsafeMemoryAccess(i);
            uint256 value = values.unsafeMemoryAccess(i);

            if (from != address(0)) {
                uint256 fromBalance = $._balances[id][from];
                if (fromBalance < value) {
                    revert ERC1155InsufficientBalance(from, fromBalance, value, id);
                }
                unchecked {
                    // Overflow not possible: value <= fromBalance
                    $._balances[id][from] = fromBalance - value;
                }
            }

            if (to != address(0)) {
                $._balances[id][to] += value;
            }
        }

        if (ids.length == 1) {
            uint256 id = ids.unsafeMemoryAccess(0);
            uint256 value = values.unsafeMemoryAccess(0);
            emit TransferSingle(operator, from, to, id, value);
        } else {
            emit TransferBatch(operator, from, to, ids, values);
        }
    }

    /**
     * @dev Version of {_update} that performs the token acceptance check by calling
     * {IERC1155Receiver-onERC1155Received} or {IERC1155Receiver-onERC1155BatchReceived} on the receiver address if it
     * contains code (eg. is a smart contract at the moment of execution).
     *
     * IMPORTANT: Overriding this function is discouraged because it poses a reentrancy risk from the receiver. So any
     * update to the contract state after this function would break the check-effect-interaction pattern. Consider
     * overriding {_update} instead.
     */
    function _updateWithAcceptanceCheck(
        address from,
        address to,
        uint256[] memory ids,
        uint256[] memory values,
        bytes memory data
    ) internal virtual {
        _update(from, to, ids, values);
        if (to != address(0)) {
            address operator = _msgSender();
            if (ids.length == 1) {
                uint256 id = ids.unsafeMemoryAccess(0);
                uint256 value = values.unsafeMemoryAccess(0);
                _doSafeTransferAcceptanceCheck(operator, from, to, id, value, data);
            } else {
                _doSafeBatchTransferAcceptanceCheck(operator, from, to, ids, values, data);
            }
        }
    }

    /**
     * @dev Transfers a `value` tokens of token type `id` from `from` to `to`.
     *
     * Emits a {TransferSingle} event.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     * - `from` must have a balance of tokens of type `id` of at least `value` amount.
     * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the
     * acceptance magic value.
     */
    function _safeTransferFrom(address from, address to, uint256 id, uint256 value, bytes memory data) internal {
        if (to == address(0)) {
            revert ERC1155InvalidReceiver(address(0));
        }
        if (from == address(0)) {
            revert ERC1155InvalidSender(address(0));
        }
        (uint256[] memory ids, uint256[] memory values) = _asSingletonArrays(id, value);
        _updateWithAcceptanceCheck(from, to, ids, values, data);
    }

    /**
     * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {_safeTransferFrom}.
     *
     * Emits a {TransferBatch} event.
     *
     * Requirements:
     *
     * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the
     * acceptance magic value.
     * - `ids` and `values` must have the same length.
     */
    function _safeBatchTransferFrom(
        address from,
        address to,
        uint256[] memory ids,
        uint256[] memory values,
        bytes memory data
    ) internal {
        if (to == address(0)) {
            revert ERC1155InvalidReceiver(address(0));
        }
        if (from == address(0)) {
            revert ERC1155InvalidSender(address(0));
        }
        _updateWithAcceptanceCheck(from, to, ids, values, data);
    }

    /**
     * @dev Sets a new URI for all token types, by relying on the token type ID
     * substitution mechanism
     * https://eips.ethereum.org/EIPS/eip-1155#metadata[defined in the EIP].
     *
     * By this mechanism, any occurrence of the `\{id\}` substring in either the
     * URI or any of the values in the JSON file at said URI will be replaced by
     * clients with the token type ID.
     *
     * For example, the `https://token-cdn-domain/\{id\}.json` URI would be
     * interpreted by clients as
     * `https://token-cdn-domain/000000000000000000000000000000000000000000000000000000000004cce0.json`
     * for token type ID 0x4cce0.
     *
     * See {uri}.
     *
     * Because these URIs cannot be meaningfully represented by the {URI} event,
     * this function emits no events.
     */
    function _setURI(string memory newuri) internal virtual {
        ERC1155Storage storage $ = _getERC1155Storage();
        $._uri = newuri;
    }

    /**
     * @dev Creates a `value` amount of tokens of type `id`, and assigns them to `to`.
     *
     * Emits a {TransferSingle} event.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the
     * acceptance magic value.
     */
    function _mint(address to, uint256 id, uint256 value, bytes memory data) internal {
        if (to == address(0)) {
            revert ERC1155InvalidReceiver(address(0));
        }
        (uint256[] memory ids, uint256[] memory values) = _asSingletonArrays(id, value);
        _updateWithAcceptanceCheck(address(0), to, ids, values, data);
    }

    /**
     * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {_mint}.
     *
     * Emits a {TransferBatch} event.
     *
     * Requirements:
     *
     * - `ids` and `values` must have the same length.
     * - `to` cannot be the zero address.
     * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the
     * acceptance magic value.
     */
    function _mintBatch(address to, uint256[] memory ids, uint256[] memory values, bytes memory data) internal {
        if (to == address(0)) {
            revert ERC1155InvalidReceiver(address(0));
        }
        _updateWithAcceptanceCheck(address(0), to, ids, values, data);
    }

    /**
     * @dev Destroys a `value` amount of tokens of type `id` from `from`
     *
     * Emits a {TransferSingle} event.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `from` must have at least `value` amount of tokens of type `id`.
     */
    function _burn(address from, uint256 id, uint256 value) internal {
        if (from == address(0)) {
            revert ERC1155InvalidSender(address(0));
        }
        (uint256[] memory ids, uint256[] memory values) = _asSingletonArrays(id, value);
        _updateWithAcceptanceCheck(from, address(0), ids, values, "");
    }

    /**
     * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {_burn}.
     *
     * Emits a {TransferBatch} event.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `from` must have at least `value` amount of tokens of type `id`.
     * - `ids` and `values` must have the same length.
     */
    function _burnBatch(address from, uint256[] memory ids, uint256[] memory values) internal {
        if (from == address(0)) {
            revert ERC1155InvalidSender(address(0));
        }
        _updateWithAcceptanceCheck(from, address(0), ids, values, "");
    }

    /**
     * @dev Approve `operator` to operate on all of `owner` tokens
     *
     * Emits an {ApprovalForAll} event.
     *
     * Requirements:
     *
     * - `operator` cannot be the zero address.
     */
    function _setApprovalForAll(address owner, address operator, bool approved) internal virtual {
        ERC1155Storage storage $ = _getERC1155Storage();
        if (operator == address(0)) {
            revert ERC1155InvalidOperator(address(0));
        }
        $._operatorApprovals[owner][operator] = approved;
        emit ApprovalForAll(owner, operator, approved);
    }

    /**
     * @dev Performs an acceptance check by calling {IERC1155-onERC1155Received} on the `to` address
     * if it contains code at the moment of execution.
     */
    function _doSafeTransferAcceptanceCheck(
        address operator,
        address from,
        address to,
        uint256 id,
        uint256 value,
        bytes memory data
    ) private {
        if (to.code.length > 0) {
            try IERC1155Receiver(to).onERC1155Received(operator, from, id, value, data) returns (bytes4 response) {
                if (response != IERC1155Receiver.onERC1155Received.selector) {
                    // Tokens rejected
                    revert ERC1155InvalidReceiver(to);
                }
            } catch (bytes memory reason) {
                if (reason.length == 0) {
                    // non-ERC1155Receiver implementer
                    revert ERC1155InvalidReceiver(to);
                } else {
                    /// @solidity memory-safe-assembly
                    assembly {
                        revert(add(32, reason), mload(reason))
                    }
                }
            }
        }
    }

    /**
     * @dev Performs a batch acceptance check by calling {IERC1155-onERC1155BatchReceived} on the `to` address
     * if it contains code at the moment of execution.
     */
    function _doSafeBatchTransferAcceptanceCheck(
        address operator,
        address from,
        address to,
        uint256[] memory ids,
        uint256[] memory values,
        bytes memory data
    ) private {
        if (to.code.length > 0) {
            try IERC1155Receiver(to).onERC1155BatchReceived(operator, from, ids, values, data) returns (
                bytes4 response
            ) {
                if (response != IERC1155Receiver.onERC1155BatchReceived.selector) {
                    // Tokens rejected
                    revert ERC1155InvalidReceiver(to);
                }
            } catch (bytes memory reason) {
                if (reason.length == 0) {
                    // non-ERC1155Receiver implementer
                    revert ERC1155InvalidReceiver(to);
                } else {
                    /// @solidity memory-safe-assembly
                    assembly {
                        revert(add(32, reason), mload(reason))
                    }
                }
            }
        }
    }

    /**
     * @dev Creates an array in memory with only one value for each of the elements provided.
     */
    function _asSingletonArrays(
        uint256 element1,
        uint256 element2
    ) private pure returns (uint256[] memory array1, uint256[] memory array2) {
        /// @solidity memory-safe-assembly
        assembly {
            // Load the free memory pointer
            array1 := mload(0x40)
            // Set array length to 1
            mstore(array1, 1)
            // Store the single element at the next word after the length (where content starts)
            mstore(add(array1, 0x20), element1)

            // Repeat for next array locating it right after the first array
            array2 := add(array1, 0x40)
            mstore(array2, 1)
            mstore(add(array2, 0x20), element2)

            // Update the free memory pointer by pointing after the second array
            mstore(0x40, add(array2, 0x40))
        }
    }
}
"
    },
    "src/contracts/program/PROGRAM.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;


import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import { NoncesUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/NoncesUpgradeable.sol";
import { GeneralReverts, ProgramReverts } from "../utils/Reverts.sol";
import { Enums, Helper, Structs } from "../utils/Utils.sol";
import { DomainSeparator } from "../utils/DomainSeparator.sol";
import { ZEC_EIP712 } from "../utils/ZEC_EIP712.sol";
import { CPTOKEN } from "../upgradeable_token/cptoken/CPTOKEN.sol";
import { MTHTLC_V1 } from "../upgradeable_HTLC/MINT_TO_PAY_HTLC/MTHTLC_V1.sol";

contract PROGRAM is Initializable, NoncesUpgradeable, DomainSeparator  {

    using Helper for uint256;
    using Helper for uint32;
    using Helper for address;
    using ZEC_EIP712 for bytes;
    
    mapping (uint256 => Structs.Program) private _program;          //  private map that maps the Program struct the program id
    mapping (uint256 => bool) private _usedProgramId;       //  private map that tracks used and existing program id to avoid duplicated ids for different owners

    mapping (uint256 => mapping(bytes32 => bytes[])) private _documentSignatures;            //  the array of signatures of a document hash mapped to a program id
    mapping (uint256 => mapping(bytes32 => mapping (bytes => bool))) private _documentSignatureMarker;
    mapping (uint256 => mapping(address => Structs.WhitelistData)) private _accountWhiteLists;
    mapping (address => bool) private _approvedProgramCreator;


    address private _cptokenAddress;
    address private _contractOwner;
    Structs.Documents private _documents;
    Structs.InitializeTokens private _initializeTokens;
    MTHTLC_V1 private _mthtlc;

    event CreateProgram(uint256 indexed _programId, address indexed _owner);         
    event InitializeToken(uint256 indexed _programId, uint256 indexed _tokenId);   
    event DiscontinueToken(uint256 indexed _programId, uint256 indexed _tokenId);   
    event AccountWhitelist(uint256 indexed _programId, address indexed _account);
    event AccountBlacklist(uint256 indexed _programId, address indexed _account);
    event SetAuthorization(address indexed _account, bool _approved);
    

    /// @dev    initializer to set cptoken address
    /// @custom:oz-upgrades
    function initialize (string calldata _eip712Name, string calldata _eip712Version) public initializer {
        _contractOwner = msg.sender;
        _setDomainSeparator(_eip712Name, _eip712Version);
        __Nonces_init();
    }

    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor() {
        _disableInitializers();
    }
   
    /// @dev    An authorized issuer create Program
    /// @notice _programId is the id of the program
    function createProgram( 

        uint256 _programId, uint256 _maximumTenor, uint256 _minimumTenor, uint256 _maximumAuthorizedOutstandingAmount, 
        uint256 _effectiveDate, uint256 _expiryDate, address _validator

        ) external {

    
        if (_approvedProgramCreator[msg.sender] == false)
            revert GeneralReverts.NotAuthorizedProgramCreator(msg.sender);

        _programId.isZero();
       
    
        if (_usedProgramId[_programId] == true)
            revert ProgramReverts.ExistingProgram(_programId);

        /// @dev validate effective date
        if ( _effectiveDate > _expiryDate )
          revert ProgramReverts.InvalidEffectiveDate();

        /// @dev    validate expiry date
        if  ( _expiryDate < block.timestamp )
            revert ProgramReverts.InvalidExpiryDate();

        _validator.revert__If__Address__Zero();
        
        _program[_programId] = Structs.Program(
                                    _programId, _maximumTenor, _minimumTenor, _maximumAuthorizedOutstandingAmount,
                                    _effectiveDate, _expiryDate, msg.sender, _validator
                                );    

        _usedProgramId[_programId] = true;
        emit CreateProgram(_programId, msg.sender);

        
        
    }


 
    /// @dev    function to fetch the details of a program using the program id
    /// @param      _programId is the id of the program that will be used to fetch the program details
    
    function getProgram(uint256 _programId) public view returns (Structs.Program memory)  {

        if (_usedProgramId[_programId] == false)
            revert ProgramReverts.ProgramDoesNotExist(_programId);

        return  _program[_programId];
        

    }
    
    /// @notice function to initialize a token
    /// @notice reverts if the token has been initialized
    /// @dev    a token can only be linked to a program
    /// @param  _tokenId is to be registered to the program
    /// @param  _programId is the program the token will be registered to
    
    function initializeToken(uint256 _tokenId, uint256 _programId, uint32 _maturityDate) external {

        _programId.isZero();
        _tokenId.isZero();
        uint256(_maturityDate).isZero();

        if (_usedProgramId[_programId] == false)
            revert ProgramReverts.ProgramDoesNotExist(_programId);

        if (msg.sender != _program[_programId]._owner)
            revert GeneralReverts.InvalidCaller(msg.sender);

        if (_initializeTokens._isInitialized[_tokenId] == true)
            revert ProgramReverts.TokenInitialized();

        revert__If__deauthorized(_programId);
        
        _initializeTokens._isInitialized[_tokenId] = true;
        _initializeTokens._tokenToProgram[_tokenId] = _programId;
        emit InitializeToken(_programId, _tokenId);

    }

    /**
        @notice deregister a token id from a program
        @dev    tokens can be discontinued even if they are still in circulation
        @notice Program link not cleared. To be kept as a reference to permit token operations 
                such as transfers except new issuances
     */ 
    function discontinueToken(uint256 _tokenId) external {

        if (_initializeTokens._isInitialized[_tokenId] == false)
            revert ProgramReverts.TokenNotInitialized(_tokenId);

        uint256 _programId = _initializeTokens._tokenToProgram[_tokenId];

        if (msg.sender != _program[_programId]._owner)
            revert GeneralReverts.InvalidCaller(msg.sender);

        _initializeTokens._isInitialized[_tokenId] = false;
        emit DiscontinueToken(_programId, _tokenId);
        
    }


    /**
        @notice Function gets the program details of an initialized / discontinued token
        @dev    Recall that program refs are retained upon `discontinueToken` function call
    */

    function getTokenProgram(uint256 _tokenId) external view returns (Structs.Program memory) {

        uint256 _programId = _initializeTokens._tokenToProgram[_tokenId];

        if (_programId == 0)
            revert ProgramReverts.NoProgramRef();

        return getProgram(_programId);

    }

    /// @dev    checks if a token has been initialized
    function isTokenInitialized(uint256 _tokenId) public view returns (bool) {

        return _initializeTokens._isInitialized[_tokenId];

    }
    
    /// @notice function to set the link to the document of a program
    /// @param  _programId is the id to the program
    function setDocument(uint256 _programId, bytes32 _documentHash, address[] memory _signers) external returns (bool success) {

        if (_usedProgramId[_programId] == false)
            revert ProgramReverts.ProgramDoesNotExist(_programId);

        if (_documentHash == bytes32(0))
            revert GeneralReverts.BytesZero();
        
        /// @dev    checks if document has been assigned index
        if (_documents._docIndex[_documentHash]._isAssigned == true)
            revert ProgramReverts.AssignedDocument();

        if (msg.sender != _program[_programId]._owner)
            revert GeneralReverts.InvalidCaller(msg.sender);

        revert__If__deauthorized(_programId);

        Structs.Document memory _document = Structs.Document(
                _documentHash, 
                _signers
        );

        _documents._programDocuments[_programId].push(_document);
        _documents._docIndex[_documentHash] = Structs.Index(true, _documents._programDocuments[_programId].length - 1);
        
        return true;

    }
    
    /// @notice function to fetch the array of links to a program using the program id
    function getProgramDocument(uint256 _programId) external view returns (Structs.Document[] memory) {

        if (_usedProgramId[_programId] == false)
            revert ProgramReverts.ProgramDoesNotExist(_programId);

        return _documents._programDocuments[_programId];

    }

    
    /// @notice function to set program signatures
    /// @dev    signer signs the hash as a message and provides the signature to this function
    
    function signDocument(uint256 _programId, ZEC_EIP712.DocumentHashData memory _hashData, bytes memory _signature) external returns (bool success) {

        ///@dev check if document was assigned index
        if (_documents._docIndex[_hashData.docHash]._isAssigned == false)
            revert ProgramReverts.UnassignedDocument();

        if (_documentSignatureMarker[_programId][_hashData.docHash][_signature] == true) 
            revert ProgramReverts.HashAssociatedWithDocument();
        
        if (_usedProgramId[_programId] == false)
            revert ProgramReverts.ProgramDoesNotExist(_programId);

        if (_signature.length != 65)
            revert GeneralReverts.InvalidSignature();

        /// @dev    get document index
        uint256 _index = _documents._docIndex[_hashData.docHash]._index;

        if (_isDocumentSigner(msg.sender, _documents._programDocuments[_programId][_index]._signers) == false)
            msg.sender.throwCallerError();

        _useCheckedNonce(msg.sender, _hashData.nonce);

        address _recoveredAddress = _signature.verifyDocumentSignature(_EIP712Domain, _hashData); 
        
        if (_recoveredAddress != msg.sender)
            revert GeneralReverts.InvalidSigner(_recoveredAddress);

        _documentSignatures[_programId][_hashData.docHash].push(_signature);
        _documentSignatureMarker[_programId][_hashData.docHash][_signature] = true;

        return true;


    }
    
    /// @notice function to fetch the program's signatures
    function getDocumentSignature(uint256 _programId, bytes32 _hash) external view returns (bytes[] memory) {

        if (_usedProgramId[_programId] == false)
            revert ProgramReverts.ProgramDoesNotExist(_programId);

        return _documentSignatures[_programId][_hash];

    }

    
    /// @notice   Whitelist account in a given program 
    function whitelistAccount(uint256 _programId, address _account, Enums.AccountType _accountType) external {

        if (_account == address(0))
            revert GeneralReverts.AddressZero();

        if (_usedProgramId[_programId] == false)
            revert ProgramReverts.ProgramDoesNotExist(_programId);

        if ((msg.sender != _program[_programId]._owner) && (msg.sender != _program[_programId]._validator))
            revert GeneralReverts.InvalidCaller(msg.sender);

        revert__If__deauthorized(_programId);
        
        _accountWhiteLists[_programId][_account] = Structs.WhitelistData(_accountType, true);
        emit AccountWhitelist(_programId, _account);

    }

    /// @notice   dewhitelist account for a given program 
    function deWhitelistAccount(uint256 _programId, address _account) external {

         if (_account == address(0))
            revert GeneralReverts.AddressZero();

        if (_usedProgramId[_programId] == false)
            revert ProgramReverts.ProgramDoesNotExist(_programId);

        if ((msg.sender != _program[_programId]._owner) && (msg.sender != _program[_programId]._validator))
            revert GeneralReverts.InvalidCaller(msg.sender);

        revert__If__deauthorized(_programId);

        _mthtlc.unlockAll(_account);
        _accountWhiteLists[_programId][_account]._isWhitelisted = false;
        
        emit AccountBlacklist(_programId, _account);
    }

    /// @dev    checks whitelist status
    function isWhitelisted(uint256 _programId, address _account) external view returns (Structs.WhitelistData memory) {
        
        if (_usedProgramId[_programId] == false)
            revert ProgramReverts.ProgramDoesNotExist(_programId);
        return _accountWhiteLists[_programId][_account];

    }

    /// @dev    get validator for a given program
    function getValidator(uint256 _programId) external view returns (address _agent) {

        if (_usedProgramId[_programId] == false)
            revert ProgramReverts.ProgramDoesNotExist(_programId);

        return _program[_programId]._validator;
    }


    /// @notice contract owner sets cptoken address
    /// @notice must not be address zero
    function setTokenContract(address _cptoken) external {

        if (msg.sender != _contractOwner)
            revert GeneralReverts.InvalidCaller(msg.sender);
        
        if (_cptoken == address(0))
            revert GeneralReverts.AddressZero();
        _cptokenAddress = _cptoken;
    }


    /// @dev    Update domain separator
    function setDomainSeparator(string calldata _eip712Name, string calldata _eip712Version) external {
        require(msg.sender == _contractOwner, "Only contract owner");
        _setDomainSeparator(_eip712Name, _eip712Version);
    }

    /// @dev    Set approval for program creation
    function setAuthorization(address _account, bool _authorize) external {

        if (msg.sender != _contractOwner)
            msg.sender.throwCallerError();
        
        _approvedProgramCreator[_account] = _authorize;
        emit SetAuthorization(_account, _authorize);
    }

    /// @dev    Checks if an account is approved to create program
    function isProgramCreatorAuthorized(address _account) public view returns (bool) {
        return _approvedProgramCreator[_account];
    }

    /// @dev    validate the signers
    function _isDocumentSigner(address _account, address[] memory _signers) internal view returns (bool) {

        for (uint256 index = 0; index < _signers.length; index++) {
            if (_account == _signers[index])
                return true;
        }

        return false;

    }
    
    /// @dev    transfer contract ownership
    function transferContractOwnerShip(address _account) external {
        _account.revert__If__Address__Zero();

        if (msg.sender != _contractOwner)
            msg.sender.throwCallerError();
        
        _contractOwner = _account;
    }

    /// @dev    set htlc
    function setHtlc(address __mthtlc) external {
         __mthtlc.revert__If__Address__Zero();
         
         if (msg.sender != _contractOwner)
            msg.sender.throwCallerError();
            
        _mthtlc = MTHTLC_V1(__mthtlc);
    }


    function revert__If__deauthorized(uint256 _programId) public {

        address _issuer = _program[_programId]._owner;
        if (isProgramCreatorAuthorized(_issuer) == false)
            revert GeneralReverts.OwnerDeauthorized();

    }
}
"
    },
    "src/contracts/program/PROGRAM_V3.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;

/// @title  Program contract
/// @author Zeconomy


import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import { NoncesUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/NoncesUpgradeable.sol";
import { GeneralReverts, ProgramReverts } from "../utils/Reverts.sol";
import { Enums, Helper, Structs } from "../utils/Utils.sol";
import { DomainSeparator } from "../utils/DomainSeparator.sol";
import { ZEC_EIP712 } from "../utils/ZEC_EIP712.sol";
import { CPTOKEN } from "../upgradeable_token/cptoken/CPTOKEN.sol";
import { MTHTLC_V1 } from "../upgradeable_HTLC/MINT_TO_PAY_HTLC/MTHTLC_V1.sol";

/// @custom:oz-upgrades-from PROGRAM_V2
contract PROGRAM_V3 is Initializable, NoncesUpgradeable, DomainSeparator  {

    using Helper for uint256;
    using Helper for uint32;
    using Helper for address;
    using ZEC_EIP712 for bytes;
    
    mapping (uint256 => Structs.Program) private _program;          //  private map that maps the Program struct the program id
    mapping (uint256 => bool) private _usedProgramId;       //  private map that tracks used and existing program id to avoid duplicated ids for different owners

    mapping (uint256 => mapping(bytes32 => bytes[])) private _documentSignatures;            //  the array of signatures of a document hash mapped to a program id
    mapping (uint256 => mapping(bytes32 => mapping (bytes => bool))) private _documentSignatureMarker;
    mapping (uint256 => mapping(address => Structs.WhitelistData)) private _accountWhiteLists;
    mapping (address => bool) private _approvedProgramCreator;

    address private _cptokenAddress;
    addre

Tags:
ERC1155, ERC165, Multisig, Non-Fungible, Upgradeable, Multi-Signature, Factory|addr:0xe0dd433372ac31c0055b7a40663033cfb3542671|verified:true|block:23484254|tx:0x729f2a0a8f20d41571730bd7aa25c42ad6f056a84c41f4d8f9a0f8e78323fb1c|first_check:1759341047

Submitted on: 2025-10-01 19:50:48

Comments

Log in to comment.

No comments yet.