Sepolia Testnet

Contract Diff Checker

Contract Name:
ChainFactory

Contract Source Code:

File 1 of 1 : ChainFactory

/*
   ________          _       ______           __                  
  / ____/ /_  ____ _(_)___  / ____/___ ______/ /_____  _______  __
 / /   / __ \/ __ `/ / __ \/ /_  / __ `/ ___/ __/ __ \/ ___/ / / /
/ /___/ / / / /_/ / / / / / __/ / /_/ / /__/ /_/ /_/ / /  / /_/ / 
\____/_/ /_/\__,_/_/_/ /_/_/    \__,_/\___/\__/\____/_/   \__, /  
                                                         /____/   

  ChainFactory Smart-Contract

  Web:      https://chainfactory.app/
  X:        https://x.com/@ChainFactory
  Telegram: https://t.me/ChainFactory
  Discord:  https://discord.gg/fpjxD39v3k
  YouTube:  https://youtu.be/ChainFactory

*/

// SPDX-License-Identifier: MIT

pragma solidity 0.8.23;

library Address {
  function isContract(address _contract) internal view returns (bool) {
    return _contract.code.length > 0;
  }
}

library Clones {
  function predictDeterministicAddress(bytes memory bytecode, bytes32 salt) internal view returns (address) {
    bytes32 hash = keccak256(abi.encodePacked(bytes1(0xff), address(this), salt, keccak256(bytecode)));

    return address(uint160(uint256(hash)));
  }

  function cloneDeterministic(bytes memory bytecode, bytes32 salt) internal returns (address result) {
    assembly {
      result := create2(0, add(bytecode, 0x20), mload(bytecode), salt)
    }

    require(result != address(0), "Deploy failed");
  }
}

interface IERC20 {
  function balanceOf(address account) external view returns (uint256);
  function allowance(address owner, address spender) external view returns (uint256);
  function approve(address spender, uint256 amount) external returns (bool);
  function transferFrom(address from, address to, uint256 amount) external returns (bool);
}

interface IMultiSignatureWallet {
  function listManagers(bool) external view returns (address[] memory);
}

interface IInitialize {
  function initialize(bytes calldata data) external;
}

abstract contract CF_Ownable {
  address internal _owner;

  event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

  modifier onlyOwner() virtual {
    require(_owner == msg.sender, "Unauthorized");

    _;
  }

  function owner() external view returns (address) {
    return _owner;
  }

  function renounceOwnership() external onlyOwner {
    _transferOwnership(address(0));
  }

  function transferOwnership(address newOwner) external onlyOwner {
    require(newOwner != address(0));

    _transferOwnership(newOwner);
  }

  function _transferOwnership(address newOwner) internal {
    address oldOwner = _owner;
    _owner = newOwner;

    emit OwnershipTransferred(oldOwner, newOwner);
  }
}

abstract contract CF_Common {
  string internal constant _version = "1.0.0";
  address internal MULTISIGN_ADDRESS;
  address internal EXECUTOR_ADDRESS;
  address internal TREASURY_ADDRESS;
  address internal FACTORY_TOKEN;
  address internal FACTORY_STAKING;
  address[] internal userList;
  uint256[] internal templateList;
  uint256 internal DISCOUNT_MIN;
  uint256 internal DISCOUNT_PCT;
  uint256 internal PRICE_MULTITRANSFER;
  uint256 internal PRICE_MULTITRANSFER_PER_WALLET;
  bool internal _locked;
  bool internal _initialized;

  mapping(uint256 => templateDataStruct) internal templateData;
  mapping(address => userDataStruct) internal userData;

  struct templateDataStruct {
    bool exists;
    bool active;
    uint256 price;
    bool discountable;
    mapping(string => templateFeatureStruct) features;
    string[] featuresList;
  }

  struct templateReturnStruct {
    uint256 id;
    bool active;
    bool discountable;
    uint256 price;
    templateFeatureReturnStruct[] features;
  }

  struct templateFeatureStruct {
    bool exists;
    uint256 price;
  }

  struct templateFeatureReturnStruct {
    string name;
    uint256 price;
  }

  struct userDataStruct {
    bool exists;
    uint256 balance;
    mapping(bytes32 => deployDataStruct) deploy;
    bytes32[] deployList;
    addedCreditDataStruct[] addedCredit;
  }

  struct deployDataStruct {
    bool exists;
    uint256 templateId;
    uint32 paidTimestamp;
    uint32 deployTimestamp;
    uint32 refundTimestamp;
    uint256 amount;
    uint256 gas;
    string[] features;
    address contractAddress;
  }

  struct addedCreditDataStruct {
    uint32 timestamp;
    uint256 amount;
    address executor;
  }

  struct userReturnStruct {
    uint256 balance;
    deployReturnStruct[] deploy;
    addedCreditDataStruct[] addedCredit;
  }

  struct deployReturnStruct {
    bytes32 receipt;
    uint256 templateId;
    uint32 paidTimestamp;
    uint32 deployTimestamp;
    uint32 refundTimestamp;
    uint256 amount;
    uint256 gas;
    string[] features;
    address contractAddress;
  }

  struct pendingDeployReturnStruct {
    address user;
    bytes32 receipt;
    uint256 templateId;
    uint256 gas;
  }

  function _timestamp() internal view returns (uint32) {
    unchecked {
      return uint32(block.timestamp % 2**32);
    }
  }

  function version() external pure returns (string memory) {
    return _version;
  }
}

contract ChainFactory is CF_Ownable, CF_Common {
  event Deposit(address indexed from, uint256 amount);
  event TemplatePaid(address indexed user, uint256 amount, uint256 gas, bytes32 receipt, uint256 templateId);
  event TemplateDeployed(address indexed user, bytes32 receipt, uint256 templateId, address contractAddress);
  event TemplateRefund(address indexed user, uint256 amount, bytes32 receipt, uint256 templateId);
  event AddedCredit(address indexed user, uint256 amount);
  event AddedGas(address indexed user, uint256 amount, bytes32 receipt, uint256 templateId);
  event RefundedGas(address indexed user, uint256 amount, bytes32 receipt, uint256 templateId);
  event BulkAddedCredit(address[] users, uint256 amount, address executor);

  modifier nonReEntrant() {
    require(!_locked, "No re-entrancy");

    _locked = true;
    _;
    _locked = false;
  }

  modifier onlyOwner() virtual override {
    require(msg.sender == _owner || (MULTISIGN_ADDRESS != address(0) && msg.sender == MULTISIGN_ADDRESS), "Unauthorized");

    _;
  }

  modifier onlyManager() {
    require(MULTISIGN_ADDRESS != address(0));

    address[] memory managers = IMultiSignatureWallet(MULTISIGN_ADDRESS).listManagers(true);

    uint256 cnt = managers.length;
    bool proceed;

    unchecked {
      for (uint256 i; i < cnt; i++) {
        if (managers[i] != msg.sender) { continue; }

        proceed = true;
      }
    }

    require(proceed, "Not manager");

    _;
  }

  modifier onlyExecutor() {
    require(msg.sender == EXECUTOR_ADDRESS, "Not executor");

    _;
  }

  modifier isTemplate(uint256 templateId) {
    require(templateData[templateId].exists && templateData[templateId].active, "Unknown template id");

    _;
  }

  function initialize() external {
    require(!_initialized);

    _transferOwnership(msg.sender);
    _initialized = true;
  }

/*
  function getDiscountInfo() external view returns (uint256, uint256) {
    return (DISCOUNT_MIN, DISCOUNT_PCT);
  }
*/

  function getMultiTransferPrice() external view returns (uint256, uint256) {
    return (PRICE_MULTITRANSFER, PRICE_MULTITRANSFER_PER_WALLET);
  }

  function setMultiTransferPrice(uint256 _price, uint256 _pricePerWallet) external onlyOwner {
    PRICE_MULTITRANSFER = _price;
    PRICE_MULTITRANSFER_PER_WALLET = _pricePerWallet;
  }

  function setTemplate(uint256 templateId, bool active, uint256 price, bool discountable, string[] calldata features, uint256[] calldata prices) external onlyOwner {
    if (!templateData[templateId].exists) {
      templateData[templateId].exists = true;
      templateList.push(templateId);
    }

    templateData[templateId].active = active;
    templateData[templateId].price = price;
    templateData[templateId].discountable = discountable;

    uint256 cnt = features.length;

    require(cnt > 0);
    require(prices.length == cnt, "Invalid number of params");

    unchecked {
      for (uint256 i; i < cnt; i++) {
        if (!templateData[templateId].features[features[i]].exists) { templateData[templateId].featuresList.push(features[i]); }

        templateData[templateId].features[features[i]] = templateFeatureStruct(true, prices[i]);
      }
    }
  }

  function _addUser(address _addr) private {
    userList.push(_addr);
    userData[_addr].exists = true;
  }

  function addCredit() public payable nonReEntrant {
    require(msg.value > 0);

    _addCredit(msg.sender, msg.value);
  }

  function _addCredit(address user, uint256 amount) private {
    if (!userData[user].exists) { _addUser(user); }

    unchecked {
      userData[user].balance += amount;
      userData[user].addedCredit.push(addedCreditDataStruct(_timestamp(), amount, user));

      emit AddedCredit(user, amount);
    }

    if (amount > 0 && TREASURY_ADDRESS != address(0)) {
      (bool success, ) = TREASURY_ADDRESS.call{ value: amount }("");

      require(success, "Transfer error");
    }
  }

  function refundGas(address user, bytes32 receipt) external payable onlyExecutor {
    require(userData[user].exists, "Unknown user");
    require(userData[user].deploy[receipt].exists, "Unknown receipt");

    (bool success, ) = payable(user).call{ value: msg.value }("");

    require(success, "Transfer error");

    emit RefundedGas(user, msg.value, receipt, userData[user].deploy[receipt].templateId);
  }

  function adminBulkAddCredit(address[] memory users, uint256 amount) external onlyManager {
    _bulkAddCredit(users, amount, msg.sender);
  }

  function execBulkAddCredit(address[] memory users, uint256 amount) external onlyExecutor {
    _bulkAddCredit(users, amount, msg.sender);
  }

  function _bulkAddCredit(address[] memory users, uint256 amount, address executor) private {
    require(amount > 0);

    uint256 cnt = users.length;

    unchecked {
      for (uint256 u; u < cnt; u++) {
        if (!userData[users[u]].exists) { _addUser(users[u]); }

        userData[users[u]].balance += amount;
        userData[users[u]].addedCredit.push(addedCreditDataStruct(_timestamp(), amount, address(this)));
      }
    }

    emit BulkAddedCredit(users, amount, executor);
  }

  function tokenMultiTransfer(address token, address[] memory target, uint256[] memory amount) external payable {
    uint256 cnt = amount.length;

    require(cnt > 0);
    require(target.length == cnt, "Invalid number of params");

    if (!userData[msg.sender].exists) { _addUser(msg.sender); }

    IERC20 iface = IERC20(token);

    unchecked {
      uint256 price = PRICE_MULTITRANSFER + (cnt * PRICE_MULTITRANSFER_PER_WALLET);
      uint256 total;

      if (msg.value != price) {
        if (msg.value > price) { revert("Amount transferred exceeds required price"); }
        if (userData[msg.sender].balance + msg.value < price) { revert("Insufficient balance"); }
      }

      for (uint256 a; a < cnt; a++) { total += amount[a]; }

      require(iface.balanceOf(msg.sender) >= total, "Insufficient balance");
      require(iface.allowance(msg.sender, address(this)) >= total, "Insufficient allowance");
    }

    if (msg.value > 0 && TREASURY_ADDRESS != address(0)) {
      (bool success, ) = TREASURY_ADDRESS.call{ value: msg.value }("");

      require(success, "Transfer error");
    }

    unchecked {
      for (uint256 a; a < cnt; a++) {
        bool success = iface.transferFrom(msg.sender, target[a], amount[a]);

        require(success, "Transfer error");
      }
    }
  }

  function payTemplate(uint256 templateId, uint256 gas, bytes32 nonce, string[] calldata features) external payable isTemplate(templateId) nonReEntrant returns (bytes32 receipt) {
    uint256 amount = msg.value;

    if (gas > 0) {
      require(EXECUTOR_ADDRESS != address(0), "No executor defined");
      require(gas <= amount, "Price error");
    }

    uint256 price = templateData[templateId].price;

    unchecked {
      uint256 cnt = features.length;

      for (uint256 i; i < cnt; i++) {
        require(templateData[templateId].features[features[i]].exists, "Unknown feature");

        price += templateData[templateId].features[features[i]].price;
      }
    }

    if (!userData[msg.sender].exists) { _addUser(msg.sender); }

/*
    if (templateData[templateId].discountable) {
      if (userData[msg.sender].balance >= DISCOUNT_MIN) { price -= templateData[templateId].price * DISCOUNT_PCT / (100*100); }
    }
*/

    unchecked {
      uint256 diff;

      if (gas > 0) { amount -= gas; }

      if (amount != price) {
        if (amount > price) {
          diff = amount - price;
          amount -= diff;

          _addCredit(msg.sender, diff);
        }

        if (amount < price && userData[msg.sender].balance + amount < price) { revert("Insufficient balance"); }
      }

      if (amount > 0 && TREASURY_ADDRESS != address(0)) {
        (bool success, ) = TREASURY_ADDRESS.call{ value: amount }("");

        require(success, "Transfer error");
      }

      if (userData[msg.sender].balance > 0) { userData[msg.sender].balance -= price - amount; }
    }

    if (gas > 0) {
      (bool success, ) = EXECUTOR_ADDRESS.call{ value: gas }("");

      require(success, "Transfer error");
    }

    receipt = keccak256(abi.encodePacked(templateId, _timestamp(), nonce, msg.sender));

    userData[msg.sender].deployList.push(receipt);
    userData[msg.sender].deploy[receipt] = deployDataStruct(true, templateId, _timestamp(), 0, 0, price, gas, features, address(0));

    emit TemplatePaid(msg.sender, price, gas, receipt, templateId);
  }

  function addGas(bytes32 receipt) external payable nonReEntrant {
    require(msg.value > 0);
    require(userData[msg.sender].exists, "Unknown user");
    require(userData[msg.sender].deploy[receipt].exists, "Unknown receipt");

    if (userData[msg.sender].deploy[receipt].contractAddress != address(0)) {
      if (userData[msg.sender].deploy[receipt].contractAddress == address(0xdEaD)) { revert("Template deployment canceled"); }

      revert("Template already deployed");
    }

    if (EXECUTOR_ADDRESS != address(0)) {
      (bool success, ) = EXECUTOR_ADDRESS.call{ value: msg.value }("");

      require(success, "Transfer error");
    }

    unchecked {
      userData[msg.sender].deploy[receipt].gas += msg.value;
    }

    emit AddedGas(msg.sender, msg.value, receipt, userData[msg.sender].deploy[receipt].templateId);
  }

  function adminCancelDeployTemplate(address user, bytes32 receipt, bool refund) external onlyManager {
    require(userData[user].exists, "Unknown user");
    require(userData[user].deploy[receipt].exists, "Unknown receipt");

    if (userData[msg.sender].deploy[receipt].contractAddress != address(0)) {
      if (userData[msg.sender].deploy[receipt].contractAddress == address(0xdEaD)) { revert("Template deployment canceled"); }

      revert("Template already deployed");
    }

    if (refund) {
      uint256 amount = userData[user].deploy[receipt].amount;

      unchecked {
        userData[user].balance += amount;
        userData[user].deploy[receipt].refundTimestamp = _timestamp();
      }

      emit TemplateRefund(user, amount, receipt, userData[user].deploy[receipt].templateId);
    }

    userData[user].deploy[receipt].contractAddress = address(0xdEaD);
  }

  function publicDeployTemplate(bytes32 receipt, bytes memory bytecode, bytes calldata data) external nonReEntrant returns (address) {
    return _deployTemplate(msg.sender, receipt, bytecode, data);
  }

  function adminDeployTemplate(address user, bytes32 receipt, bytes memory bytecode, bytes calldata data) external onlyManager nonReEntrant returns (address) {
    return _deployTemplate(user, receipt, bytecode, data);
  }

  function execDeployTemplate(address user, bytes32 receipt, bytes memory bytecode, bytes calldata data) external onlyExecutor nonReEntrant returns (address) {
    return _deployTemplate(user, receipt, bytecode, data);
  }

  function _deployTemplate(address user, bytes32 receipt, bytes memory bytecode, bytes calldata data) private returns (address contractAddress) {
    require(userData[user].exists, "Unknown user");
    require(userData[user].deploy[receipt].exists, "Unknown receipt");

    if (userData[msg.sender].deploy[receipt].contractAddress != address(0)) {
      if (userData[msg.sender].deploy[receipt].contractAddress == address(0xdEaD)) { revert("Template deployment canceled"); }

      revert("Template already deployed");
    }

    address predictAddress = Clones.predictDeterministicAddress(bytecode, receipt);

    require(!Address.isContract(predictAddress),"Contract already exists");

    contractAddress = Clones.cloneDeterministic(bytecode, receipt);

    require(predictAddress == contractAddress, "Deployed address is not the predicted one");

    userData[user].deploy[receipt].deployTimestamp = _timestamp();
    userData[user].deploy[receipt].contractAddress = contractAddress;

    emit TemplateDeployed(user, receipt, userData[user].deploy[receipt].templateId, contractAddress);

    if (data.length > 0) {
      try IInitialize(contractAddress).initialize(data) { } catch { }
    }
  }

  function listTemplates() external view returns (templateReturnStruct[] memory data) {
    uint256 cnt = templateList.length;
    uint256 len = _countActiveTemplates();
    uint256 i;

    data = new templateReturnStruct[](len);

    unchecked {
      for (uint256 t; t < cnt; t++) {
        if (!templateData[templateList[t]].active) { continue; }

/*
        uint256 price = templateData[templateList[t]].price;

        if (templateData[templateList[t]].discountable && userData[msg.sender].exists) {
          if (userData[msg.sender].balance >= DISCOUNT_MIN) { price -= templateData[templateList[t]].price * DISCOUNT_PCT / (100*100); }
        }
*/

        uint256 fcnt = templateData[templateList[t]].featuresList.length;

        templateFeatureReturnStruct[] memory features = new templateFeatureReturnStruct[](fcnt);

        for (uint256 f; f < fcnt; f++) {
          string memory _feature = templateData[templateList[t]].featuresList[f];

          features[f] = templateFeatureReturnStruct(_feature, templateData[templateList[t]].features[_feature].price);
        }

        data[i++] = templateReturnStruct(templateList[t], true, templateData[templateList[t]].discountable, templateData[templateList[t]].price, features);
      }
    }
  }

  function listTemplates(bool activeOnly) external view onlyManager returns (templateReturnStruct[] memory data) {
    uint256 cnt = templateList.length;
    uint256 len = activeOnly ? _countActiveTemplates() : cnt;
    uint256 i;

    data = new templateReturnStruct[](len);

    unchecked {
      for (uint256 t; t < cnt; t++) {
        if (!activeOnly && !templateData[templateList[t]].active) { continue; }

/*
        uint256 price = templateData[templateList[t]].price;

        if (templateData[templateList[t]].discountable && userData[msg.sender].exists) {
          if (userData[msg.sender].balance >= DISCOUNT_MIN) { price -= templateData[templateList[t]].price * DISCOUNT_PCT / (100*100); }
        }
*/

        uint256 fcnt = templateData[templateList[t]].featuresList.length;

        templateFeatureReturnStruct[] memory features = new templateFeatureReturnStruct[](fcnt);

        for (uint256 f; f < fcnt; f++) {
          string memory _feature = templateData[templateList[t]].featuresList[f];

          features[f] = templateFeatureReturnStruct(_feature, templateData[templateList[t]].features[_feature].price);
        }

        data[i++] = templateReturnStruct(templateList[t], templateData[templateList[t]].active, templateData[templateList[t]].discountable, templateData[templateList[t]].price, features);
      }
    }
  }

  function _countPendingDeploys() private view returns (uint256 pending) {
    uint256 cnt = userList.length;

    unchecked {
      for (uint256 u; u < cnt; u++) {
        uint256 dcnt = userData[userList[u]].deployList.length;

        if (dcnt == 0) { continue; }

        for (uint256 d; d < dcnt; d++) {
          bytes32 receipt =  userData[userList[u]].deployList[d];

          if (userData[userList[u]].deploy[receipt].contractAddress != address(0) || userData[userList[u]].deploy[receipt].contractAddress != address(0xdEad) || userData[userList[u]].deploy[receipt].gas == 0) { continue; }

          pending++;
        }
      }
    }
  }

  function _countActiveTemplates() private view returns (uint256 active) {
    uint256 cnt = templateList.length;

    unchecked {
      for (uint256 t; t < cnt; t++) {
        if (!templateData[templateList[t]].active) { continue; }

        active++;
      }
    }
  }

  function adminListPendingDeploys() external view onlyManager returns (pendingDeployReturnStruct[] memory) {
    return _listPendingDeploys();
  }

  function execListPendingDeploys() external view onlyExecutor returns (pendingDeployReturnStruct[] memory) {
    return _listPendingDeploys();
  }

  function _listPendingDeploys() private view returns (pendingDeployReturnStruct[] memory data) {
    uint256 cnt = userList.length;
    uint256 pcnt = _countPendingDeploys();
    uint256 i;

    data = new pendingDeployReturnStruct[](pcnt);

    unchecked {
      for (uint256 u; u < cnt; u++) {
        uint256 dcnt = userData[userList[u]].deployList.length;

        if (dcnt == 0) { continue; }

        for (uint256 d; d < dcnt; d++) {
          bytes32 receipt = userData[userList[u]].deployList[d];
          deployDataStruct memory deploy = userData[userList[u]].deploy[receipt];

          if (deploy.contractAddress != address(0) || deploy.contractAddress != address(0xdEaD) || deploy.gas == 0) { continue; }

          data[i++] = pendingDeployReturnStruct(userList[u], receipt, deploy.templateId, deploy.gas);
        }
      }
    }
  }

  function listUsers() external view returns (address[] memory data) {
    uint256 cnt = userList.length;
    uint256 i;

    data = new address[](cnt);

    unchecked {
      for (uint256 u; u < cnt; u++) { data[i++] = userList[u]; }
    }
  }

  function getUserInfo(address _user) external view returns (userReturnStruct memory user) {
    require(userData[_user].exists, "Unknown user");

    uint256 cnt = userData[_user].deployList.length;

    user.balance = userData[_user].balance;
    user.deploy = new deployReturnStruct[](cnt);
    user.addedCredit = userData[_user].addedCredit;

    unchecked {
      for (uint256 d; d < cnt; d++) {
        bytes32 receipt = userData[_user].deployList[d];
        deployDataStruct memory deploy = userData[_user].deploy[receipt];

        user.deploy[d] = deployReturnStruct(receipt, deploy.templateId, deploy.paidTimestamp, deploy.deployTimestamp, deploy.refundTimestamp, deploy.amount, deploy.gas, deploy.features, deploy.contractAddress);
      }
    }
  }

  function withdrawETH(address payable to, uint256 amount) external onlyOwner nonReEntrant {
    (bool success, ) = to.call{ value: amount }("");

    require(success);
  }

/*
  function setDiscount(uint256 _min, uint256 _pct) external onlyOwner {
    require(_pct < 100*100);

    DISCOUNT_MIN = _min;
    DISCOUNT_PCT = _pct;
  }
*/

  function setMultiSignatureWallet(address _address) external onlyOwner {
    MULTISIGN_ADDRESS = _address;
  }

  function setExecutor(address payable _address) external onlyOwner {
    EXECUTOR_ADDRESS = _address;
  }

  function setTreasury(address payable _address) external onlyOwner {
    TREASURY_ADDRESS = _address;
  }

  receive() external payable { addCredit(); }
  fallback() external payable { }
}

Please enter a contract address above to load the contract details and source code.

Context size (optional):