S Price: $0.865451 (+0.93%)

Contract Diff Checker

Contract Name:
EmoliumBridgeDeposit

Contract Source Code:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "./sources/IMintableERC20.sol";
import "./sources/OZ/IERC20.sol";
import "./sources/OZ/ReentrancyGuard.sol";
import "./sources/OZ/ERC20Burnable.sol";
import "./sources/OZ/Ownable.sol";
import "./chiefValidator.sol";
import "./sources/IWETH.sol";
import "./chainRouter.sol";

contract EmoliumBridgeDeposit is ReentrancyGuard, Ownable {
    ChiefValidator public chiefValidator;
    IWETH public weth;
    ChainRouter public chainRouter;
    uint256 defaultNetworkId = 1;

    // Store Deposit Proofs
    mapping(bytes32 => bool) public depositProofs;

    // Event to generate proof
    event DepositProofGenerated(address indexed depositor, bytes32 proofHash, string proofData);

    // Event for ERC20 or WETH deposit
    event ERC20Deposited(address indexed user, address indexed token, uint256 amount);
    event ETHDeposited(address indexed user, uint256 amount);
    event ERC20Bridged(address indexed token, uint256 amount);

    constructor(address payable _chiefValidator, address _initialOwner, address _weth, address _chainRouter) Ownable(_initialOwner) {
        chiefValidator = ChiefValidator(_chiefValidator);
        weth = IWETH(_weth);
        chainRouter = ChainRouter(_chainRouter);
        transferOwnership(_initialOwner);
    }

    // Deposit ETH and generate proof
    function depositETH(string memory chainDestination, uint256 networkId) external payable nonReentrant {
        require(msg.value > 0, "Must send ETH");

        require(chainRouter.isChainRegistered(networkId), "Destination chain not registered");

        weth.deposit{value: msg.value}();

        uint256 amount = msg.value;
        require(weth.transfer(address(chiefValidator), amount), "Transfer to ChiefValidator failed");

        string memory proofData = _generateProofData(msg.sender, address(weth), amount, chainDestination);
        bytes32 proofHash = keccak256(abi.encodePacked(proofData));

        depositProofs[proofHash] = true;

        emit ETHDeposited(msg.sender, amount);
        emit DepositProofGenerated(msg.sender, proofHash, proofData);
    }

    // Deposit ERC20 and generate proof
    function depositERC20(address token, uint256 amount, string memory chainDestination, uint256 networkId) external nonReentrant {
        require(amount > 0, "Amount must be greater than 0");

        require(chainRouter.isChainRegistered(networkId), "Destination chain not registered");

        require(IERC20(token).transferFrom(msg.sender, address(this), amount), "Transfer failed");

        string memory proofData = _generateProofData(msg.sender, token, amount, chainDestination);
        bytes32 proofHash = keccak256(abi.encodePacked(proofData));

        depositProofs[proofHash] = true;

        if (chiefValidator.isBridgeTokenRegistered(token)) {
            _burnERC20(token, amount);
        } else {
            require(IERC20(token).transfer(address(chiefValidator), amount), "Transfer to ChiefValidator failed");
            emit ERC20Deposited(msg.sender, token, amount);
        }

        emit DepositProofGenerated(msg.sender, proofHash, proofData);
    }

    // Function to burn ERC20 tokens
    function _burnERC20(address token, uint256 amount) internal {
        ERC20Burnable burnableToken = ERC20Burnable(token);
        burnableToken.burn(amount);
        emit ERC20Bridged(token, amount);
    }

    // Generate a string with the deposit information
    function _generateProofData(address depositor, address token, uint256 amount, string memory chainDestination) internal view returns (string memory) {
        return string(abi.encodePacked(
            toAsciiString(depositor), ",", 
            toAsciiString(token), ",", 
            uint2str(amount), ",", 
            uint2str(block.timestamp), ",", 
            chainDestination
        ));
    }

    // Function to verify a proof
    function verifyDepositProof(bytes32 proofHash) external view returns (bool) {
        return depositProofs[proofHash];
    }

    // Allow the contract admin to save ERC20 tokens not burned
    function withdrawERC20(address token, uint256 amount) external onlyOwner {
        require(IERC20(token).balanceOf(address(this)) >= amount, "Insufficient balance");
        IERC20(token).transfer(owner(), amount);
    }

    // Allow the contract admin to save WETH
    function withdrawWETH(uint256 amount) external onlyOwner {
        require(weth.balanceOf(address(this)) >= amount, "Insufficient WETH balance");
        weth.transfer(owner(), amount);
    }

    // Function to receive ETH without automatically wrapping them
    receive() external payable {
        revert("Please use depositETH function with chainDestination and networkId");
    }

    // Utility : Convert an address to string (hexadecimal)
    function toAsciiString(address x) internal pure returns (string memory) {
        bytes memory s = new bytes(40);
        for (uint256 i = 0; i < 20; i++) {
            bytes1 b = bytes1(uint8(uint256(uint160(x)) / (2**(8*(19 - i)))));
            bytes1 hi = bytes1(uint8(b) / 16);
            bytes1 lo = bytes1(uint8(b) - 16 * uint8(hi));
            s[2*i] = char(hi);
            s[2*i+1] = char(lo);
        }
        return string(s);
    }

    function char(bytes1 b) internal pure returns (bytes1 c) {
        if (uint8(b) < 10) return bytes1(uint8(b) + 0x30);
        else return bytes1(uint8(b) + 0x57);
    }

    // Utility : Convert a uint to string
    function uint2str(uint256 _i) internal pure returns (string memory str) {
        if (_i == 0) return "0";
        uint256 j = _i;
        uint256 len;
        while (j != 0) {
            len++;
            j /= 10;
        }
        bytes memory bstr = new bytes(len);
        uint256 k = len;
        while (_i != 0) {
            k = k-1;
            uint8 temp = (48 + uint8(_i - _i / 10 * 10));
            bytes1 b1 = bytes1(temp);
            bstr[k] = b1;
            _i /= 10;
        }
        str = string(bstr);
    }

    function setChiefValidator(address payable _chiefValidator) external onlyOwner {
        require(_chiefValidator != address(0), "Invalid address");
        uint256 codeSize;
        assembly {
            codeSize := extcodesize(_chiefValidator)
        }
        require(codeSize > 0, "Address is not a contract");

        chiefValidator = ChiefValidator(_chiefValidator);
    }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract ChainRouter {

    struct ChainInfo {
        string chainName;
        uint256 networkId;
    }

    // Mapping to store registered chains
    mapping(uint256 => ChainInfo) public registeredChains;
    uint256[] public networkIds;

    // Event for chain management
    event ChainRegistered(string chainName, uint256 networkId);
    event ChainUnregistered(string chainName, uint256 networkId);

    // Contract admin for chain registration
    address public owner;

    modifier onlyOwner() {
        require(msg.sender == owner, "Not authorized");
        _;
    }

    constructor() {
        owner = msg.sender;
    }

    // Function to register a new destination chain
    function registerChain(string calldata chainName, uint256 networkId) external onlyOwner {
        require(registeredChains[networkId].networkId == 0, "Chain already registered");

        registeredChains[networkId] = ChainInfo(chainName, networkId);
        networkIds.push(networkId);

        emit ChainRegistered(chainName, networkId);
    }

    // Function to unregister a chain
    function unregisterChain(uint256 networkId) external onlyOwner {
        require(registeredChains[networkId].networkId != 0, "Chain not registered");

        string memory chainName = registeredChains[networkId].chainName;
        delete registeredChains[networkId];

        // Remove networkId from the array
        for (uint256 i = 0; i < networkIds.length; i++) {
            if (networkIds[i] == networkId) {
                networkIds[i] = networkIds[networkIds.length - 1];
                networkIds.pop();
                break;
            }
        }

        emit ChainUnregistered(chainName, networkId);
    }

    // Function to check if a chain is registered
    function isChainRegistered(uint256 networkId) external view returns (bool) {
        return registeredChains[networkId].networkId != 0;
    }

    // Function to get chain info
    function getChainInfo(uint256 networkId) external view returns (string memory, uint256) {
        require(registeredChains[networkId].networkId != 0, "Chain not registered");

        ChainInfo memory chain = registeredChains[networkId];
        return (chain.chainName, chain.networkId);
    }

    // Function to get all registered chains
    function getAllRegisteredChains() external view returns (ChainInfo[] memory) {
        ChainInfo[] memory chains = new ChainInfo[](networkIds.length);
        for (uint256 i = 0; i < networkIds.length; i++) {
            chains[i] = registeredChains[networkIds[i]];
        }
        return chains;
    }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IWETH {
    function deposit() external payable;
    function withdraw(uint256 wad) external;
    function balanceOf(address guy) external view returns (uint256);
    function transfer(address dst, uint256 wad) external returns (bool);
    function approve(address usr, uint256 wad) external returns (bool);
    function transferFrom(address src, address dst, uint256 wad) external returns (bool);
    function allowance(address src, address dst) external view returns (uint256);
    event Deposit(address indexed dst, uint256 wad);
    event Withdrawal(address indexed src, uint256 wad);
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "./sources/OZ/Ownable.sol";
import "./sources/OZ/ReentrancyGuard.sol";
import "./sources/OZ/IERC20.sol";
import "./sources/OZ/Address.sol";
import "./sources/IWETH.sol";
import "./sources/IMintableERC20.sol";
import "./sources/IBridgedToken.sol";
import "./validatorFactory.sol";
import "./validator.sol";


interface IBurnableERC20 {
    function burn(uint256 amount) external;
}
contract ChiefValidator is Ownable, ReentrancyGuard {
    address public EMLTokenAddress;
    ValidatorFactory public validatorFactory;
    uint256 public emlRewardAmount;
    address public bridgeDeposit;
    address public wethAddress;
    address public devAddress;
    IWETH public weth;
    
    // Retreive Bridge Tokens
    mapping(address => bool) public bridgeTokens;
    // Store Proofs
    mapping(bytes32 => bool) public proofs; // bool => true (legit) or false (not submitted or fraud)
    // Mapping of authorized Contracts
    mapping(address => bool) public authorizedContracts;

    // Events
    event TokenDeployed(address indexed initialOwner, address indexed tokenAddress, string name, string symbol);
    event ProofVerified(bytes32 indexed proofHash, bool valid);
    event EMLMinted(address indexed validator, uint256 amount);
    event BridgeTokenRegistered(address indexed tokenAddress);
    event BridgedETH(address to, uint256 amount);
    event BridgeFee(address to, uint256 amount);
    event TokenBridgedIn(address indexed tokenAddress, address to, uint256 amount);
    event BridgedTokenTransfered(address indexed tokenAddress, address to, uint256 amount);
    event DeployerAuthorized(address indexed contractAddress, bool isAuthorized);

    // Modifier
    modifier onlyAuthorized() {
        require(authorizedContracts[msg.sender], "Caller is not authorized");
        _;
    }

    constructor(
        address _EMLTokenAddress,
        address _wethAddress,
        address _devAddress,
        address _initialOwner,
        address _validatorFactory
    ) Ownable(_initialOwner) 
    {
        EMLTokenAddress = _EMLTokenAddress;
        validatorFactory = ValidatorFactory(_validatorFactory);
        wethAddress = _wethAddress;
        devAddress = _devAddress;
        transferOwnership(_initialOwner);
    }

    // Autorize contracts for validator creation
    function setAuthorization(address _contract, bool _isAuthorized) external onlyOwner {
        authorizedContracts[_contract] = _isAuthorized;
        emit DeployerAuthorized(_contract, _isAuthorized);
    }

    // Register Bridge Tokens
    function registerBridgeTokens(address[] calldata tokens) external onlyAuthorized {
        for (uint256 i = 0; i < tokens.length; i++) {
            bridgeTokens[tokens[i]] = true;
            emit BridgeTokenRegistered(tokens[i]);
        }
    }

    // Update Bridge Tokens
    function updateBridgeTokens(address tokenAddress, bool isRegistered) external onlyOwner {
        bridgeTokens[tokenAddress] = isRegistered;
    }

    function isBridgeTokenRegistered(address token) external view returns (bool) {
        return bridgeTokens[token];
    }
    
    // Verify Proof & Mint EML Rewards to validators
    function verifyProof(string[] calldata proofData, bytes32 proofHash) external nonReentrant onlyOwner {
        require(proofData.length == 6, "Invalid proof data length");
        require(!proofs[proofHash], "Proof already submitted");

        address[] memory validators = validatorFactory.getDeployedValidators();
        uint256 activeValidatorCount = 0;
        address[] memory activeValidators = new address[](validators.length);

        for (uint256 i = 0; i < validators.length; i++) {
            if (Validator(validators[i]).isActive()) {
                activeValidators[activeValidatorCount] = validators[i];
                activeValidatorCount++;
            }
        }

        require(activeValidatorCount > 0, "No active validators found");

        bool proofValid = false;
        for (uint256 i = 0; i < activeValidatorCount; i++) {
            bytes32 keccakResult = Validator(activeValidators[i]).calculateKeccak256(proofData);
            if (keccakResult == proofHash) {
                proofValid = true;
                break;
            }
        }

        if (proofValid) {
            mintEMLRewards();
            handleBridgedToken(proofData);
            proofs[proofHash] = true;
            emit ProofVerified(proofHash, true);
        } else {
            emit ProofVerified(proofHash, false);
        }
    }

    // Mint EML Rewards
    function mintEMLRewards() internal {
        address[] memory validators = validatorFactory.getDeployedValidators();
        uint256 totalActiveValidators = 0;

        // Number of active Validators
        for (uint256 i = 0; i < validators.length; i++) {
            if (Validator(validators[i]).isActive()) {
                totalActiveValidators++;
            }
        }

        require(totalActiveValidators > 0, "No active validators found");

        uint256 rewardPerValidator = emlRewardAmount / totalActiveValidators;

        // EML Mint to Active Validators only & equally 
        for (uint256 i = 0; i < validators.length; i++) {
            if (Validator(validators[i]).isActive()) {
                address validatorOwner = Validator(validators[i]).getValidatorOwner();
                // Call EML Mint function
                IMintableERC20(EMLTokenAddress).mint(validatorOwner, rewardPerValidator);
                emit EMLMinted(validatorOwner, rewardPerValidator);
            }
        }
    }

    // Handle Bridge Operations
    function handleBridgedToken(string[] calldata proofData) internal {
        address tokenAddress = parseAddress(proofData[0]);
        uint256 amount = parseUint(proofData[1]);
        address destination = parseAddress(proofData[2]);
        uint256 timestamp = parseUint(proofData[3]);
        string memory emoliumSaltData = proofData[4];
        uint256 providedFee = parseUint(proofData[5]);

        require(timestamp >= 0, "Invalid timestamp");
        require(bytes(emoliumSaltData).length > 0, "Invalid salt data");
        require(providedFee >= 0, "Invalid fee");

        // Fee calculation and validation
        uint256 calculatedFee = (amount * providedFee) / 10000;
        uint256 netAmount = amount - calculatedFee;
        
        // Handle ETH & WETH
        if (tokenAddress == wethAddress) {
            IWETH(wethAddress).withdraw(amount);
            payable(devAddress).transfer(calculatedFee);
            payable(destination).transfer(netAmount);
            emit BridgedETH(destination, netAmount);
            if (calculatedFee > 0) emit BridgeFee(devAddress, calculatedFee);
        }
        // Handle Emolium Bridge Registered Token
        else if (bridgeTokens[tokenAddress]) {
            IBridgedToken(tokenAddress).mint(destination, netAmount);
            IBridgedToken(tokenAddress).mint(devAddress, calculatedFee);
            emit TokenBridgedIn(tokenAddress, destination, netAmount);
            if (calculatedFee > 0) emit BridgeFee(devAddress, calculatedFee);
        }
        // Handle all other ERC20 tokens
        else {
            IERC20(tokenAddress).transfer(destination, netAmount);
            IERC20(tokenAddress).transfer(devAddress, calculatedFee);
            emit BridgedTokenTransfered(tokenAddress, destination, netAmount);
            if (calculatedFee > 0) emit BridgeFee(devAddress, calculatedFee);
        }
    }

    // Convert string to uint
    function parseUint(string memory _value) internal pure returns (uint256) {
        bytes memory b = bytes(_value);
        uint256 number = 0;
        for (uint256 i = 0; i < b.length; i++) {
            number = number * 10 + (uint256(uint8(b[i])) - 48);
        }
        return number;
    }

    // Convert string to address
    function parseAddress(string memory _value) internal pure returns (address) {
        bytes memory b = bytes(_value);
        require(b.length == 42, "Invalid address length"); // Inclut '0x' au début

        uint160 number;
        for (uint256 i = 2; i < b.length; i++) { // Skip '0x'
            number *= 16;
            uint8 c = uint8(b[i]);
            if (c >= 48 && c <= 57) {
                number += c - 48;
            } else if (c >= 97 && c <= 102) {
                number += c - 87;
            } else if (c >= 65 && c <= 70) {
                number += c - 55;
            }
        }
        return address(number);
    }

    // Setters for addresses
    function setBridgeDeposit(address _bridgeDeposit) external onlyOwner {
        bridgeDeposit = _bridgeDeposit;
    }

    function setWethAddress(address _wethAddress) external onlyOwner {
        wethAddress = _wethAddress;
    }

    // Set EML Reward Amount
    function setEMLRewardAmount(uint256 _amount) external onlyOwner {
        emlRewardAmount = _amount;
    }

    // Get Proof Status
    function getProofStatus(bytes32 proofHash) external view returns (bool) {
        return proofs[proofHash];
    }

    // Emergency & Rescue Functions
    function rescueETH() external onlyOwner {
        require(address(this).balance > 0, "Insufficient ETH balance");
        payable(devAddress).transfer(address(this).balance);
    }

    function rescueERC20(address token, uint256 amount) external onlyOwner {
        IERC20 erc20 = IERC20(token);
        require(erc20.balanceOf(address(this)) >= amount, "Insufficient token balance");
        erc20.transfer(devAddress, amount);
    }

    function rescueWETH(uint256 amount) external onlyOwner {
        require(weth.balanceOf(address(this)) >= amount, "Insufficient WETH balance");
        weth.transfer(devAddress, amount);
    }

    receive() external payable {}

}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)

pragma solidity ^0.8.20;

import  "./Context.sol";

abstract contract Ownable is Context {
    address private _owner;

    error OwnableUnauthorizedAccount(address account);
    error OwnableInvalidOwner(address owner);

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

    constructor(address initialOwner) {
        if (initialOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(initialOwner);
    }

    modifier onlyOwner() {
        _checkOwner();
        _;
    }

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

    function _checkOwner() internal view virtual {
        if (owner() != _msgSender()) {
            revert OwnableUnauthorizedAccount(_msgSender());
        }
    }

    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    function transferOwnership(address newOwner) public virtual onlyOwner {
        if (newOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(newOwner);
    }

    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/ERC20Burnable.sol)

pragma solidity ^0.8.20;

import "./ERC20.sol";
import "./Context.sol";

abstract contract ERC20Burnable is Context, ERC20 {

    function burn(uint256 value) public virtual {
        _burn(_msgSender(), value);
    }

    function burnFrom(address account, uint256 value) public virtual {
        _spendAllowance(account, _msgSender(), value);
        _burn(account, value);
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/ReentrancyGuard.sol)

pragma solidity ^0.8.20;

abstract contract ReentrancyGuard {

    uint256 private constant NOT_ENTERED = 1;
    uint256 private constant ENTERED = 2;
    uint256 private _status;

    error ReentrancyGuardReentrantCall();

    constructor() {
        _status = NOT_ENTERED;
    }
    
    modifier nonReentrant() {
        _nonReentrantBefore();
        _;
        _nonReentrantAfter();
    }

    function _nonReentrantBefore() private {
        if (_status == ENTERED) {
            revert ReentrancyGuardReentrantCall();
        }

        _status = ENTERED;
    }

    function _nonReentrantAfter() private {
        _status = NOT_ENTERED;
    }

    function _reentrancyGuardEntered() internal view returns (bool) {
        return _status == ENTERED;
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.20;

interface IERC20 {

    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(address indexed owner, address indexed spender, uint256 value);
    
    function totalSupply() external view returns (uint256);

    function balanceOf(address account) external view returns (uint256);

    function transfer(address to, uint256 value) external returns (bool);

    function allowance(address owner, address spender) external view returns (uint256);

    function approve(address spender, uint256 value) external returns (bool);

    function transferFrom(address from, address to, uint256 value) external returns (bool);
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IMintableERC20 {
    function mint(address to, uint256 amount) external;
    function burn(address from, uint256 amount) external;
    function transfer(address to, uint256 amount) external returns (bool);
    function transferFrom(address from, address to, uint256 amount) external returns (bool);
    function approve(address spender, uint256 amount) external returns (bool);
    function allowance(address owner, address spender) external view returns (uint256);
    function totalSupply() external view returns (uint256);
    function balanceOf(address account) external view returns (uint256);
    function decimals() external view returns (uint8);
    function symbol() external view returns (string memory);
    function name() external view returns (string memory);
    function tokenAddress() external view returns (address);
    function mintable() external view returns (bool);
    function burnable() external view returns (bool);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)

pragma solidity ^0.8.20;

abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }

    function _contextSuffixLength() internal view virtual returns (uint256) {
        return 0;
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/ERC20.sol)

pragma solidity ^0.8.20;

import "./IERC20.sol";
import "./IERC20Metadata.sol";
import "./Context.sol";
import "./draft-IERC6093.sol";

abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors {

    mapping(address account => uint256) private _balances;
    mapping(address account => mapping(address spender => uint256)) private _allowances;

    uint256 private _totalSupply;

    string private _name;
    string private _symbol;

    constructor(string memory name_, string memory symbol_) {
        _name = name_;
        _symbol = symbol_;
    }

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

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

    function decimals() public view virtual returns (uint8) {
        return 18;
    }

    function totalSupply() public view virtual returns (uint256) {
        return _totalSupply;
    }

    function balanceOf(address account) public view virtual returns (uint256) {
        return _balances[account];
    }

    function transfer(address to, uint256 value) public virtual returns (bool) {
        address owner = _msgSender();
        _transfer(owner, to, value);
        return true;
    }

    function allowance(address owner, address spender) public view virtual returns (uint256) {
        return _allowances[owner][spender];
    }

    function approve(address spender, uint256 value) public virtual returns (bool) {
        address owner = _msgSender();
        _approve(owner, spender, value);
        return true;
    }

    function transferFrom(address from, address to, uint256 value) public virtual returns (bool) {
        address spender = _msgSender();
        _spendAllowance(from, spender, value);
        _transfer(from, to, value);
        return true;
    }

    function _transfer(address from, address to, uint256 value) internal {
        if (from == address(0)) {
            revert ERC20InvalidSender(address(0));
        }
        if (to == address(0)) {
            revert ERC20InvalidReceiver(address(0));
        }
        _update(from, to, value);
    }

    function _update(address from, address to, uint256 value) internal virtual {
        if (from == address(0)) {
            _totalSupply += value;
        } else {
            uint256 fromBalance = _balances[from];
            if (fromBalance < value) {
                revert ERC20InsufficientBalance(from, fromBalance, value);
            }
            unchecked {
                _balances[from] = fromBalance - value;
            }
        }

        if (to == address(0)) {
            unchecked {
                _totalSupply -= value;
            }
        } else {
            unchecked {
                _balances[to] += value;
            }
        }

        emit Transfer(from, to, value);
    }

    function _mint(address account, uint256 value) internal {
        if (account == address(0)) {
            revert ERC20InvalidReceiver(address(0));
        }
        _update(address(0), account, value);
    }

    function _burn(address account, uint256 value) internal {
        if (account == address(0)) {
            revert ERC20InvalidSender(address(0));
        }
        _update(account, address(0), value);
    }

    function _approve(address owner, address spender, uint256 value) internal {
        _approve(owner, spender, value, true);
    }

    function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual {
        if (owner == address(0)) {
            revert ERC20InvalidApprover(address(0));
        }
        if (spender == address(0)) {
            revert ERC20InvalidSpender(address(0));
        }
        _allowances[owner][spender] = value;
        if (emitEvent) {
            emit Approval(owner, spender, value);
        }
    }

    function _spendAllowance(address owner, address spender, uint256 value) internal virtual {
        uint256 currentAllowance = allowance(owner, spender);
        if (currentAllowance != type(uint256).max) {
            if (currentAllowance < value) {
                revert ERC20InsufficientAllowance(spender, currentAllowance, value);
            }
            unchecked {
                _approve(owner, spender, currentAllowance - value, false);
            }
        }
    }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "./sources/OZ/IERC20.sol";
import "./sources/OZ/Ownable.sol";

contract Validator is Ownable {
    address public EMLTokenAddress;
    address public validatorFactory;
    uint256 public validatorPrice;
    address public admin;
    bool public active = false;

    // Validator Current Owner
    mapping(address => address) public validatorOwners;

    // Mapping to associate the contract address with the validator address
    mapping(address => address) public validatorContracts;
    // Events
    event EMLBalanceChecked(uint256 balance);
    event ValidatorCount(uint256 activeCount, uint256 maxCount);
    event ValidatorClosed(address indexed owner, uint256 amount);
    event ValidatorOpened();
    event ValidatorClosedEvent();

    constructor(address _EMLTokenAddress, address _initialOwner, address _validatorFactory) Ownable(_initialOwner) {
        EMLTokenAddress = _EMLTokenAddress;
        admin = _initialOwner;
        validatorFactory = _validatorFactory; // Set the validatorFactory by default
        validatorContracts[address(this)] = address(this);
        validatorOwners[address(this)] = _initialOwner;
    }
    
    // Keccak hash function for an array of strings
    function calculateKeccak256(string[] calldata inputs) external view returns (bytes32) {
        require(active, "Validator is not active"); // Check if the validator is active

        // Concatenate all strings in the array
        bytes memory concatenatedString;
        for (uint256 i = 0; i < inputs.length; i++) {
            concatenatedString = abi.encodePacked(concatenatedString, inputs[i]);
        }

        // Calculate the Keccak256 hash of the concatenated string
        bytes32 hash = keccak256(concatenatedString);
        return hash;
    }

    // Set a new validator factory
    function setValidatorFactory(address _validatorFactory) external onlyOwner {
        require(_validatorFactory != address(0), "Invalid validator factory address");
        validatorFactory = _validatorFactory;
    }

    // Return the validator contract address
    function getValidatorContractAddress() external view returns (address) {
        return validatorContracts[address(this)];
    }

    // Return the current validator owner
    function getValidatorOwner() external view returns (address) {
        return validatorOwners[address(this)];
    }

    // Return the amount of EML available in the contract
    function getEMLBalance() external view returns (uint256) {
        return IERC20(EMLTokenAddress).balanceOf(address(this));
    }

    // Function to close the validator and send the EML balance to the owner
    function closeValidator() external onlyOwner {
        uint256 balance = IERC20(EMLTokenAddress).balanceOf(address(this));
        require(balance > 0, "No EML tokens to transfer");

        IERC20(EMLTokenAddress).transfer(owner(), balance);
        active = false;

        emit ValidatorClosed(owner(), balance);
        emit ValidatorClosedEvent();
    }

    // Open the validator if it holds at least 20,000 EML
    function openValidator() external onlyOwner {
        uint256 emlBalance = IERC20(EMLTokenAddress).balanceOf(address(this));
        emit EMLBalanceChecked(emlBalance); // Log la balance

        require(emlBalance >= 20000 * 10**18, "Validator must hold at least 20,000 EML");
        active = true;
        emit ValidatorOpened();
    }

    // Function to check if the validator is active
    function isActive() external view returns (bool) {
        return active;
    }

    // Function to get the admin address
    function getAdmin() external view returns (address) {
        return admin;
    }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "./sources/OZ/Ownable.sol";
import "./validator.sol";

contract ValidatorFactory is Ownable {

    // Mapping to store the addresses of deployed validators
    mapping(address => bool) public validators;
    // Mapping of authorized Contracts
    mapping(address => bool) public authorizedContracts;
    // List of deployed validators
    address[] public deployedValidators;
    // Maximum number of active validators
    uint256 public constant MAX_ACTIVE_VALIDATORS = 25;

    // Events
    event ValidatorCreated(address indexed validatorAddress);
    event ContractAuthorized(address indexed contractAddress, bool isAuthorized);

    // Modifier
    modifier onlyAuthorized() {
        require(authorizedContracts[msg.sender], "Caller is not authorized");
        _;
    }

    constructor(address _initialOwner) Ownable(_initialOwner) {
    } 

    // Autorize contracts for validator creation
    function setAuthorization(address _contract, bool _isAuthorized) external onlyOwner {
        authorizedContracts[_contract] = _isAuthorized;
        emit ContractAuthorized(_contract, _isAuthorized);
    }

    // Function to create a single Validator and assign ownership to the provided owner
    function createValidator(address _EMLTokenAddress, address _owner) external onlyAuthorized returns (address) {
        // Check the number of active validators
        uint256 activeValidatorCount = 0;
        for (uint256 i = 0; i < deployedValidators.length; i++) {
            if (Validator(deployedValidators[i]).isActive()) {
                activeValidatorCount++;
            }
        }

        require(
            activeValidatorCount < MAX_ACTIVE_VALIDATORS,
            "Maximum number of active validators reached"
        );

        // Create the validator and pass this factory address in the constructor
        Validator newValidator = new Validator(_EMLTokenAddress, _owner, address(this));

        // Register the new validator in the factory
        address validatorAddress = address(newValidator);
        validators[validatorAddress] = true;
        deployedValidators.push(validatorAddress);

        emit ValidatorCreated(validatorAddress);
        return validatorAddress;
    }

    // Create a batch of Validators
    function createInitialValidators(address _EMLTokenAddress, uint256 numberOfValidators) external onlyOwner {
        require(numberOfValidators > 0, "Number of validators must be greater than 0");
        require(numberOfValidators <= MAX_ACTIVE_VALIDATORS, "Cannot create more than the maximum allowed validators");
        
        // Check active Validators number
        uint256 activeValidatorCount = 0;
        for (uint256 i = 0; i < deployedValidators.length; i++) {
            if (Validator(deployedValidators[i]).isActive()) {
                activeValidatorCount++;
            }
        }

        require(
            deployedValidators.length + numberOfValidators <= MAX_ACTIVE_VALIDATORS || activeValidatorCount < MAX_ACTIVE_VALIDATORS,
            "Cannot exceed the maximum number of validators"
        );

        for (uint256 i = 0; i < numberOfValidators; i++) {
            Validator newValidator = new Validator(_EMLTokenAddress, msg.sender, address(this));
            address validatorAddress = address(newValidator);
            
            validators[validatorAddress] = true;
            deployedValidators.push(validatorAddress);

            emit ValidatorCreated(validatorAddress);
        }
    }

    // Check if a validator is active
    function isValidatorActive(address validatorAddress) external view returns (bool) {
        require(validators[validatorAddress], "Validator not found");
        return Validator(validatorAddress).isActive();
    }

    // Get the owner of a validator
    function getValidatorOwner(address validatorAddress) external view returns (address) {
        require(validators[validatorAddress], "Validator not found");
        return Validator(validatorAddress).getValidatorOwner();
    }

    // Get the list of deployed validators
    function getDeployedValidators() external view returns (address[] memory) {
        return deployedValidators;
    }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IBridgedToken {
    function mint(address to, uint256 amount) external;
    function burn(address from, uint256 amount) external;
    function burnFrom(address from, uint256 amount) external;
    function transfer(address to, uint256 amount) external returns (bool);
    function transferFrom(address from, address to, uint256 amount) external returns (bool);
    function approve(address spender, uint256 amount) external returns (bool);
    function allowance(address owner, address spender) external view returns (uint256);
    function totalSupply() external view returns (uint256);
    function balanceOf(address account) external view returns (uint256);
    function decimals() external view returns (uint8);
    function symbol() external view returns (string memory);
    function name() external view returns (string memory);
    function tokenAddress() external view returns (address);
    function mintable() external view returns (bool);
    function burnable() external view returns (bool);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Address.sol)

pragma solidity ^0.8.20;

import "./Errors.sol";

library Address {
    error AddressEmptyCode(address target);
    function sendValue(address payable recipient, uint256 amount) internal {
        if (address(this).balance < amount) {
            revert Errors.InsufficientBalance(address(this).balance, amount);
        }

        (bool success, ) = recipient.call{value: amount}("");
        if (!success) {
            revert Errors.FailedCall();
        }
    }

    function functionCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0);
    }

    function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
        if (address(this).balance < value) {
            revert Errors.InsufficientBalance(address(this).balance, value);
        }
        (bool success, bytes memory returndata) = target.call{value: value}(data);
        return verifyCallResultFromTarget(target, success, returndata);
    }

    function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
        (bool success, bytes memory returndata) = target.staticcall(data);
        return verifyCallResultFromTarget(target, success, returndata);
    }

    function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
        (bool success, bytes memory returndata) = target.delegatecall(data);
        return verifyCallResultFromTarget(target, success, returndata);
    }

    function verifyCallResultFromTarget(
        address target,
        bool success,
        bytes memory returndata
    ) internal view returns (bytes memory) {
        if (!success) {
            _revert(returndata);
        } else {
            if (returndata.length == 0 && target.code.length == 0) {
                revert AddressEmptyCode(target);
            }
            return returndata;
        }
    }

    function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) {
        if (!success) {
            _revert(returndata);
        } else {
            return returndata;
        }
    }

    function _revert(bytes memory returndata) private pure {
        // Look for revert reason and bubble it up if present
        if (returndata.length > 0) {
            // The easiest way to bubble the revert reason is using memory via assembly
            assembly ("memory-safe") {
                let returndata_size := mload(returndata)
                revert(add(32, returndata), returndata_size)
            }
        } else {
            revert Errors.FailedCall();
        }
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/draft-IERC6093.sol)
pragma solidity ^0.8.20;

interface IERC20Errors {

    error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed);
    error ERC20InvalidSender(address sender);
    error ERC20InvalidReceiver(address receiver);
    error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed);
    error ERC20InvalidApprover(address approver);
    error ERC20InvalidSpender(address spender);
}

interface IERC721Errors {

    error ERC721InvalidOwner(address owner);
    error ERC721NonexistentToken(uint256 tokenId);
    error ERC721IncorrectOwner(address sender, uint256 tokenId, address owner);
    error ERC721InvalidSender(address sender);
    error ERC721InvalidReceiver(address receiver);
    error ERC721InsufficientApproval(address operator, uint256 tokenId);
    error ERC721InvalidApprover(address approver);
    error ERC721InvalidOperator(address operator);
}

interface IERC1155Errors {

    error ERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 tokenId);
    error ERC1155InvalidSender(address sender);
    error ERC1155InvalidReceiver(address receiver);
    error ERC1155MissingApprovalForAll(address operator, address owner);
    error ERC1155InvalidApprover(address approver);
    error ERC1155InvalidOperator(address operator);
    error ERC1155InvalidArrayLength(uint256 idsLength, uint256 valuesLength);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Metadata.sol)

pragma solidity ^0.8.20;

import "./IERC20.sol";

interface IERC20Metadata is IERC20 {
    
    function name() external view returns (string memory);

    function symbol() external view returns (string memory);

    function decimals() external view returns (uint8);
}

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

library Errors {
    
    error InsufficientBalance(uint256 balance, uint256 needed);
    error FailedCall();
    error FailedDeployment();
    error MissingPrecompile(address);
}

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

Context size (optional):