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 { }
}