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
Submitted on: 2025-10-01 19:50:48
Comments
Log in to comment.
No comments yet.