pragma solidity >=0.5.0 <0.6.0;

/**
Author: Sik Jean Soon
**/

/**
 * @title SafeMath for uint256
 * @dev Math operations with safety checks that throw on error
 */
library SafeMathUint256 {
    /**
    * @dev Multiplies two numbers, throws on overflow.
    */
    function mul(uint256 a, uint256 b) internal pure returns (uint256 c) {
        if (a == 0) {
            return 0;
        }
        c = a * b;
        require(c / a == b, "Multiplier exception");
        return c;
    }

    /**
    * @dev Integer division of two numbers, truncating the quotient.
    */
    function div(uint256 a, uint256 b) internal pure returns (uint256) {
        return a / b; // Solidity automatically throws when dividing by 0
    }

    /**
    * @dev Subtracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend).
    */
    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
        require(b <= a, "Subtraction exception");
        return a - b;
    }

    /**
    * @dev Adds two numbers, throws on overflow.
    */
    function add(uint256 a, uint256 b) internal pure returns (uint256 c) {
        c = a + b;
        require(c >= a, "Addition exception");
        return c;
    }

    /**
    * @dev Divides two numbers and returns the remainder (unsigned integer modulo),
    * reverts when dividing by zero.
    */
    function mod(uint256 a, uint256 b) internal pure returns (uint256) {
        require(b != 0, "Modulo exception");
        return a % b;
    }

}

/**
 * @title SafeMath for uint8
 * @dev Math operations with safety checks that throw on error
 */
library SafeMathUint8 {
    /**
    * @dev Multiplies two numbers, throws on overflow.
    */
    function mul(uint8 a, uint8 b) internal pure returns (uint8 c) {
        if (a == 0) {
            return 0;
        }
        c = a * b;
        require(c / a == b, "Multiplier exception");
        return c;
    }

    /**
    * @dev Integer division of two numbers, truncating the quotient.
    */
    function div(uint8 a, uint8 b) internal pure returns (uint8) {
        return a / b; // Solidity automatically throws when dividing by 0
    }

    /**
    * @dev Subtracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend).
    */
    function sub(uint8 a, uint8 b) internal pure returns (uint8) {
        require(b <= a, "Subtraction exception");
        return a - b;
    }

    /**
    * @dev Adds two numbers, throws on overflow.
    */
    function add(uint8 a, uint8 b) internal pure returns (uint8 c) {
        c = a + b;
        require(c >= a, "Addition exception");
        return c;
    }

    /**
    * @dev Divides two numbers and returns the remainder (unsigned integer modulo),
    * reverts when dividing by zero.
    */
    function mod(uint8 a, uint8 b) internal pure returns (uint8) {
        require(b != 0, "Modulo exception");
        return a % b;
    }

}

contract Common {
    bytes32 internal LABEL_CODE_STAKER;
    bytes32 internal LABEL_CODE_STAKER_CONTROLLER;
    bytes32 internal LABEL_CODE_SIGNER_CONTROLLER;
    bytes32 internal LABEL_CODE_SIGNER;
    bytes32 internal LABEL_CODE_BACKSYS;
    bytes32 internal LABEL_CODE_OPS;

    uint8 constant internal MAX_WALLET = 64;
    uint256 constant internal WALLET_FLAG_ALL = (2 ** (uint256(MAX_WALLET))) - 1;

    constructor() public
    {
        LABEL_CODE_STAKER = encodePacked("STAKER");
        LABEL_CODE_STAKER_CONTROLLER = encodePacked("STAKER_CONTROLLER");
        LABEL_CODE_SIGNER_CONTROLLER = encodePacked("SIGNER_CONTROLLER");
        LABEL_CODE_SIGNER = encodePacked("SIGNER");
        LABEL_CODE_BACKSYS = encodePacked("BACKSYS");
        LABEL_CODE_OPS = encodePacked("OPS");
    }

    function encodePacked(string memory s) internal pure
        returns (bytes32)
    {
        return keccak256(abi.encodePacked(s));
    }

    function convertBytesToBytes4(bytes memory _in) internal pure
        returns (bytes4 out)
    {
        if (0 == _in.length)
            return 0x0;

        assembly {
            out := mload(add(_in, 32))
        }
    }

    function isContract(address _address) internal view
        returns (bool)
    {
        uint32 size;
        assembly {
            size := extcodesize(_address)
        }
        return (0 < size);
    }

}

contract Label is Common {
    string public class;
    string public label;
    string public description;

    bytes32 public classCode;
    bytes32 public labelCode;

    constructor(string memory _class, string memory _label, string memory _description) public
    {
        class = _class;        
        label = _label;
        description = _description;

        classCode = encodePacked(_class);
        labelCode = encodePacked(_label);
    }

}


contract MultiSigNode is Label {
    using SafeMathUint8 for uint8;

    address payable public root;
    address public parent;

    // wallet index => wallet address
    mapping(uint8 => address) public wallets;
    // wallet address => wallet bit position
    mapping(address => uint8) public walletsIndex;

    // Normal wallet
    uint8 public walletCount;
    // Total wallet = normal wallet + contract wallet
    uint8 public totalWallet;

    modifier onlyRoot() {
        require(msg.sender == root, "Node.onlyRoot: Access denied");
        _;
    }

    constructor(address payable _root, address[] memory _wallets, string memory _label, string memory _description) public
        Label("NODE", _label, _description)
    {
        require(address(0) != _root, "Node: Root address is empty");
        require(MAX_WALLET >= _wallets.length, "Node: Wallet list exceeded limit");

        root = _root;

        for (uint8 i = 1; _wallets.length >= i; i = i.add(1)) {
            address wallet = _wallets[i.sub(1)];

            require(address(0) != wallet, "Node: Wallet address is empty");
            require(0 == walletsIndex[wallet], "Node: Duplicated wallet address");

            wallets[i] = wallet;
            walletsIndex[wallet] = i;

            if (!isContract(wallet))
                walletCount = walletCount.add(1);
        }

        totalWallet = uint8(_wallets.length);
    }

    function init(address _parent) external
        onlyRoot
    {
        parent = _parent;

        if (0 < totalWallet) {
            uint8 count = 0;

            for (uint8 i = 1; i <= MAX_WALLET && count <= totalWallet; i = i.add(1)) {
                address wallet = wallets[i];

                if (address(0) != wallet) {
                    count = count.add(1);

                    // Notify root this attached wallet, root will set parent address (if this is node)
                    MultiSigRoot(root).attachWalletOrNode(wallet);
                }
            }
        }
    }

    function term() external
        onlyRoot
    {
        if (0 < totalWallet) {
            uint8 count = 0;

            for (uint8 i = 1; i <= MAX_WALLET && count <= totalWallet; i = i.add(1)) {
                address wallet = wallets[i];

                if (address(0) != wallet) {
                    count = count.add(1);

                    // Notify root to remove this wallet from list
                    MultiSigRoot(root).detachWalletOrNode(wallet);
                }
            }
        }
    }

    function attach(uint8 _index, address _wallet) external
        onlyRoot
        returns (bool)
    {
        require(0 < _index && MAX_WALLET >= _index, "Node.attach: Index out of range");
        require(address(0) != _wallet, "Node.attach: Wallet address is empty");
        require(0 == walletsIndex[_wallet], "Node.attach: Duplicated wallet address");

        if (address(0) != wallets[_index])
            detach(wallets[_index]);

        walletsIndex[_wallet] = _index;
        wallets[_index] = _wallet;

        if (!isContract(_wallet))
            walletCount = walletCount.add(1);

        totalWallet = totalWallet.add(1);

        // Notify root this attached wallet, root will trigger attach operation (if this wallet is a contract)
        MultiSigRoot(root).attachWalletOrNode(_wallet);

        return true;
    }

    function detach(address _wallet) public
        onlyRoot
        returns (bool)
    {
        require(address(0) != _wallet, "Node.detach: Wallet address is empty");

        uint8 index = walletsIndex[_wallet];
        require(0 < index && MAX_WALLET >= index, "Node.detach: Wallet address is not registered");

        if (!isContract(_wallet))
            walletCount = walletCount.sub(1);

        totalWallet = totalWallet.sub(1);

        delete wallets[index];
        delete walletsIndex[_wallet];

        // Notify root to remove this wallet from list
        MultiSigRoot(root).detachWalletOrNode(_wallet);

        return true;
    }

    function getRootNode() external view
        returns (address)
    {
        if (address(0) == parent)
            return address(this);

        return MultiSigNode(parent).getRootNode();
    }

}


/**
 * NOTE: Regulator is meant for changable rules for multi-sig
 */
contract MultiSigRegulator is Label {
    using SafeMathUint8 for uint8;
    using SafeMathUint256 for uint256;

    event TransactionLimitChanged(string requirementType, uint256 limit);

    address payable public root;

    address private creator;

    // Cached parameters
    address private argTo;
    uint256 private argValue;

    bool public isSealed;

    // Daily transaction limit (mapped: requirement type => TransactionLimit)
    mapping(bytes32 => TransactionLimit) public transactionLimits;

    struct TransactionLimit {
        uint256 datetime;
        uint256 volume;
        uint256 upperLimit;
    }

    modifier onlySealed() {
        require(isSealed, "Regulator.onlySealed: Not sealed");
        _;
    }

    modifier onlyMe() {
        require(msg.sender == address(this), "Regulator.onlyMe: Access denied");
        _;
    }

    modifier onlyRoot() {
        require(msg.sender == root, "Regulator.onlyRoot: Access denied");
        _;
    }

    modifier onlyCreator() {
        require(msg.sender == creator, "Regulator.onlyCreator: Access denied");
        _;
    }

    /**
     * Supported non-payable default function
     */
    function () external
        onlyMe
        onlySealed
    {
        revert("Regulator: Not supported");
    }

    constructor(address payable _root, string memory _label, string memory _description) public
        Label("REGULATOR", _label, _description)
    {
        require(address(0) != _root, "Regulator: Root address is empty");
        root = _root;
        creator = msg.sender;
    }

    /**
     * Supported non-payable function: ERC_ER_SHI.increaseSupply
     * Only can be called by this contract itself to resolve calldata
     */
    function increaseSupply(uint256 _value, address /* _to */) external
        onlyMe
        onlySealed
    {
        defaultRequirement("increaseSupply", _value);
    }

    /**
     * Supported non-payable function: ERC_ER_SHI.decreaseSupply
     * Only can be called by this contract itself to resolve calldata
     */
    function decreaseSupply(uint256 _value, address /* _from */) external
        onlyMe
        onlySealed
    {
        defaultRequirement("decreaseSupply", _value);
    }

    /**
     * Supported non-payable function: ERC_ER_SHI.freeze
     * Only can be called by this contract itself to resolve calldata
     */
    function freeze(address /* _from */, uint256 /* _value */) external
        onlyMe
        onlySealed
    {
        requirement1Backsys();
    }

    /**
     * Supported non-payable function: ERC_ER_SHI.unfreeze
     * Only can be called by this contract itself to resolve calldata
     */
    function unfreeze(address /* _from */, uint256 /* _value */) external
        onlyMe
        onlySealed
    {
        requirement1Backsys();
    }

    /**
     * Supported non-payable function: ERC_ER_SHI.freezeAddress
     * Only can be called by this contract itself to resolve calldata
     */
    function freezeAddress(address /* _addressOf */) external
        onlyMe
        onlySealed
    {
        requirement1Backsys();
    }

    /**
     * Supported non-payable function: ERC_ER_SHI.unfreezeAddress
     * Only can be called by this contract itself to resolve calldata
     */
    function unfreezeAddress(address /* _addressOf */) external
        onlyMe
        onlySealed
    {
        requirement1Backsys();
    }

    /**
     * Supported non-payable function: Ownership.acceptOwnership
     * Only can be called by this contract itself to resolve calldata
     */
    function acceptOwnership () external
        onlyMe
        onlySealed
    {
        requirement(LABEL_CODE_OPS, 2, 1); // INDEX 2: ONE SIGNABLE
        requirement(LABEL_CODE_SIGNER_CONTROLLER, 1, 1); // INDEX 1: ONE SIGNABLE
    }

    /**
     * Supported non-payable function: Ownership.transferOwnership
     * Only can be called by this contract itself to resolve calldata
     */
    function transferOwnership (address payable /* _newOwner */) external
        onlyMe
        onlySealed
    {
        requirement(LABEL_CODE_STAKER, WALLET_FLAG_ALL, 1); // ANY ONE SIGNABLE
        requirement(LABEL_CODE_STAKER_CONTROLLER, WALLET_FLAG_ALL, uint8(-1)); // ALL SIGNABLE
        requirement(LABEL_CODE_SIGNER_CONTROLLER, WALLET_FLAG_ALL, 1); // ANY ONE SIGNABLE
    }

    /**
     * Supported non-payable function: Controllable.pause
     * Only can be called by this contract itself to resolve calldata
     */
    function pause () external
        onlyMe
        onlySealed
    {
        requirement(LABEL_CODE_STAKER_CONTROLLER, WALLET_FLAG_ALL, 1); // ANY ONE SIGNABLE
    }

    /**
     * Supported non-payable function: Controllable.resume
     * Only can be called by this contract itself to resolve calldata
     */
    function resume () external
        onlyMe
        onlySealed
    {
        requirement(LABEL_CODE_STAKER_CONTROLLER, WALLET_FLAG_ALL, 2); // ANY TWO SIGNABLE
    }

    /**
     * Supported non-payable function: MultiSigRegulator.setTransactionLimit
     */
    function setTransactionLimit(string calldata _requirementType, uint256 _limit) external
    {
        if (msg.sender == root || !isSealed) {
            // Set transaction limit
            transactionLimits[encodePacked(_requirementType)].upperLimit = _limit;
            emit TransactionLimitChanged(_requirementType, _limit);
        }
        else {
            require(msg.sender == address(this), "Regulator.setTransactionLimit: Access denied");

            // Create requirements for this transaction
            requirement(LABEL_CODE_STAKER_CONTROLLER, WALLET_FLAG_ALL, 2); // ANY TWO SIGNABLE
        }
    }

    function seal() external
        onlyCreator
    {
        require(!isSealed, "Regulator.seal: Access denied");
        isSealed = true;
    }

    function createRequirement(uint256 /* _transactionCode */, address /* _from */, address _to, uint256 _value, bytes calldata _data) external
        onlyRoot
    {
        // Cache parameters
        argTo = _to;
        argValue = _value;

        // Perform self call to determine function details for requirement setup
        (bool success, bytes memory returnData) = address(this).call.value(_value)(_data);

        if (!success) {
            // Check the error string is available or not
            if (0 == returnData.length || bytes4(0x08c379a0) != convertBytesToBytes4(returnData))
                revert("Regulator.createRequirement: Function call failed");
            else {
                bytes memory bytesArray = new bytes(returnData.length);
                for (uint256 i = 0; i < returnData.length.sub(4); i = i.add(1)) {
                    bytesArray[i] = returnData[i.add(4)];
                }

                (string memory reason) = abi.decode(bytesArray, (string));
                revert(reason);
            }
        }
    }

    function requirement(bytes32 _labelCode, uint256 _flag, uint8 _required) private
    {
        MultiSigRoot(root).createRequirement(_labelCode, _flag, _required);
    }

    function defaultRequirement(string memory _requirementType, uint256 _value) private
    {
        bytes32 t = encodePacked(_requirementType);

        // Check transaction volume limit
        TransactionLimit storage limit = transactionLimits[t];

        // Check transaction volume limit
        if (0 < limit.upperLimit) {
            // Truncate timestamp (YYYYMMDD) to daily (UTC time)
            uint256 dt = now - (now % 86400);

            if (dt == limit.datetime)
                limit.volume = limit.volume.add(_value);
            else {
                // Reset volume on new day
                limit.datetime = dt;
                limit.volume = _value;
            }

            require(limit.upperLimit >= limit.volume, "Regulator.defaultRequirement: Exceeded limit");
        }

        // Create requirement for this transaction
        requirement(LABEL_CODE_OPS, WALLET_FLAG_ALL, 4); // ANY FOUR SIGNABLE
    }

    function requirement1Backsys() private
    {
        requirement(LABEL_CODE_BACKSYS, WALLET_FLAG_ALL, 1); // ANY ONE SIGNABLE
    }

}


contract MultiSigRoot is Label {
    using SafeMathUint8 for uint8;
    using SafeMathUint256 for uint256;

    uint8 constant private WALLET_TYPE_WALLET = 1;
    uint8 constant private WALLET_TYPE_NODE = 2;

    uint8 constant private TRANSACTION_STATUS_EMPTY = 0;
    uint8 constant private TRANSACTION_STATUS_PENDING = 1;
    uint8 constant private TRANSACTION_STATUS_EXECUTED = 2;
    uint8 constant private TRANSACTION_STATUS_FAILURE = 3;
    uint8 constant private TRANSACTION_STATUS_REVOKED = 4;

    event Confirmation(address indexed sender, uint256 indexed transactionCode);
    event Revocation(address indexed sender, uint256 indexed transactionCode);
    event Submission(uint256 indexed transactionCode);
    event Requirement(uint256 indexed transactionCode, bytes32 labelCode, uint256 flag, uint8 required);
    event Execution(uint256 indexed transactionCode);
    event ExecutionFailure(uint256 indexed transactionCode);
    event Deposit(address indexed sender, uint256 value);

    event StakersChanged(address indexed stakers);
    event SignersChanged(address indexed signers);
    event RegulatorChanged(address indexed regulator);
    event StakersControllerChanged(address indexed stakersController);
    event SignersControllerChanged(address indexed signersController);
    
    event WalletOrNodeAttached(address indexed wallet);
    event WalletOrNodeDetached(address indexed wallet);
    
    address public stakers;
    address public signers;

    address public stakersController;
    address public signersController;

    address public regulator;

    // Transaction (mapped: transactionCode => Transaction)
    mapping(uint256 => Transaction) public transactions;
    uint256 public transactionCode;

    // Requirement (mapped: transactionCode + label code => requirement)
    mapping(uint256 => mapping(bytes32 => TransactionRequirement)) public requirements;
    // Prevent duplicated confirmation (mapped: transactionCode + wallet address => bool)
    mapping(uint256 => mapping(address => bool)) public confirmations;

    // Registered wallets or nodes (mapped: address => type)
    mapping(address => uint8) public registered;

    // Search node address by wallet address (mapped: wallet address => node address)
    mapping(address => address) public walletToNodes;

    // Search wallet index by wallet address (mapped: wallet address => wallet index)
    mapping(address => uint8) private walletToIndexes;

    // Search label code by wallet address (mapped: wallet address => label code)
    mapping(address => bytes32) private walletToLabelCodes;

    // Search node address by label code (mapped: label code => node address)
    mapping(bytes32 => address) private labelCodeToNodes;

    struct Transaction {
        uint8 status;
        uint8 totalRequired;

        address to;
        uint256 value;
        bytes data;
        string reason;
    }

    struct TransactionRequirement {
        uint8 required;
        uint256 flag;
    }

    modifier onlyEligible(uint256 _transactionCode) {
        require(isEligible(_transactionCode, msg.sender), "Root.onlyEligible: Not eligible");
        _;
    }

    modifier onlySignable(uint256 _transactionCode) {
        require(isSignable(_transactionCode, msg.sender), "Root.onlySignable: Not signable");
        _;
    }

    modifier onlyNode() {
        require(WALLET_TYPE_NODE == registered[msg.sender], "Root.onlyNode: Access denied");
        _;
    }

    modifier onlyWallet() {
        require(WALLET_TYPE_WALLET == registered[msg.sender], "Root.onlyWallet: Access denied");
        require(!isContract(msg.sender), "Root.onlyWallet: Is not node");
        _;
    }

    modifier onlyRegulator() {
        require(msg.sender == regulator, "Root.onlyRegulator: Access denied");
        _;
    }

    constructor(string memory _label, string memory _description) public
        Label("ROOT", _label, _description)
    {
    }

    function () external payable
    {
        if (0 < msg.value)
            emit Deposit(msg.sender, msg.value);
    }

    function isEligible(uint256 _transactionCode, address _sender) public view
        returns (bool)
    {
        uint256 flag = requirements[_transactionCode][walletToLabelCodes[_sender]].flag;

        if (0 != flag) {
            uint8 index = walletToIndexes[_sender];

            if (0 != index) {
                index = index.sub(1);

                // Check the bit is on for wallet index
                return (0 != ((flag >> index) & 1));
            }
        }
        return false;
    }

    function isSignable(uint256 _transactionCode, address _sender) public view
        returns (bool)
    {
        if (TRANSACTION_STATUS_PENDING == transactions[_transactionCode].status) {
            if (!confirmations[_transactionCode][_sender]) {
                if (0 != requirements[_transactionCode][walletToLabelCodes[_sender]].required)
                    return true;
            }
        }
        return false;
    }

    function createRequirement(bytes32 _labelCode, uint256 _flag, uint8 _required) external
        onlyRegulator
    {
        setRequirement(_labelCode, _flag, _required);
    }

    function setRequirement(bytes32 _labelCode, uint256 _flag, uint8 _required) private
    {
        require(0 < _flag, "Root.setRequirement: Confirmation flag is empty");

        uint8 totalRequired;

        // Required all wallet in the node
        if (uint8(-1) == _required) {
            address node = labelCodeToNodes[_labelCode];
            require(address(0) != node, "Root.setRequirement: Node is not found");

            totalRequired = MultiSigNode(node).walletCount();

            if (node != signers) {
                // Stakers and controllers must have at least 1 wallet attached
                require(0 < totalRequired, "Root.setRequirement: No wallet");
            }
            else {
                // Signer node allowed to be empty
                if (0 == totalRequired)
                    return;
            }

            require(0 < totalRequired, "Root.setRequirement: Confirmation required is empty");
        }
        else {
            // allowed 0 requirement, in order to support eligible user but not signable (view transaction only)
            totalRequired = _required;
        }

        require(0 == requirements[transactionCode][_labelCode].flag, "Root.setRequirement: Duplicated requirement");

        requirements[transactionCode][_labelCode] = TransactionRequirement({
            required: totalRequired,
            flag: _flag
        });

        // Increase total required in transaction
        transactions[transactionCode].totalRequired = transactions[transactionCode].totalRequired.add(totalRequired);

        emit Requirement(transactionCode, _labelCode, _flag, totalRequired);
    }

    function submit(address _to, uint256 _value, bytes calldata _data) external
        onlyWallet
        returns (uint256 /* transactionCode */) 
    {
        require(address(0) != _to, "Root.submit: Target address is empty");

        // Generate transaction id
        transactionCode = transactionCode.add(1);

        bytes4 functionId = convertBytesToBytes4(_data);

        // Create requirement that based on destination address
        if (address(this) != _to) {
            // Check this is node address or not
            if (WALLET_TYPE_NODE == registered[_to]) {
                // Calling node function
                // - 0x80882800: node.attach
                // - 0xceb6c343: node.detach
                if (bytes4(0x80882800) == functionId || bytes4(0xceb6c343) == functionId) { // node.attach or node.detach
                    address rootNode = MultiSigNode(_to).getRootNode();

                    if (rootNode == signers) {
                        // Change SIGNER need ALL SIGNER_CONTROLLER
                        setRequirement(LABEL_CODE_SIGNER_CONTROLLER, WALLET_FLAG_ALL, uint8(-1)); // ALL signable
                    }
                    else if (rootNode == signersController || rootNode == stakersController) {
                        // Change SIGNERS_CONTROLLER or STAKER_CONTROLLER need ALL STAKER
                        setRequirement(LABEL_CODE_STAKER, WALLET_FLAG_ALL, uint8(-1)); // ALL signable
                    }
                    else if (rootNode == stakers) {
                        // Change STAKER need ALL STAKER_CONTROLLER
                        setRequirement(LABEL_CODE_STAKER_CONTROLLER, WALLET_FLAG_ALL, uint8(-1)); // ALL signable
                    }
                    else {
                        revert("Root.submit: Unknown node");
                    }
                }
                else
                    revert("Root.submit: Not supported");
            }
            else {
                // Regulator create requirement
                MultiSigRegulator(regulator).createRequirement(transactionCode, msg.sender, _to, _value, _data);
            }
        }
        else {
            // Calling self functions
            // - 0xcde0a4f8: root.setRegulator
            // - 0xb47876ea: root.setSigners
            // - 0xc27dbe63: root.setStakers
            // - 0x26bc178c: root.setStakersController
            // - 0x51d996bf: root.setSignersController
            if (bytes4(0xcde0a4f8) == functionId || bytes4(0xc27dbe63) == functionId) // setRegulator or setStakers
                setRequirement(LABEL_CODE_STAKER_CONTROLLER, WALLET_FLAG_ALL, uint8(-1)); // ALL signable
            else if (bytes4(0x26bc178c) == functionId || bytes4(0x51d996bf) == functionId) // setStakersController or setSignersController
                setRequirement(LABEL_CODE_STAKER, WALLET_FLAG_ALL, uint8(-1)); // ALL signable
            else if (bytes4(0xb47876ea) == functionId) // setSigners
                setRequirement(LABEL_CODE_SIGNER_CONTROLLER, WALLET_FLAG_ALL, uint8(-1)); // ALL signable
            else
                revert("Root.submit: Not supported");
        }

        require(0 < transactions[transactionCode].totalRequired, "Root.submit: Requirement is empty");

        // Create transaction structure
        transactions[transactionCode] = Transaction({
            status: TRANSACTION_STATUS_PENDING,
            totalRequired: transactions[transactionCode].totalRequired,
            to: _to,
            value: _value,
            data: _data,
            reason: ""
        });

        emit Submission(transactionCode);

        // Confirm transaction if eligible and signable
        if (isEligible(transactionCode, msg.sender) && isSignable(transactionCode, msg.sender))
            confirmTransaction(transactionCode, transactions[transactionCode]);

        return transactionCode;
    }

    function confirm(uint256 _transactionCode) external
        onlyWallet
        onlyEligible(_transactionCode)
        onlySignable(_transactionCode)
        returns (bool)
    {
        Transaction storage transaction = transactions[_transactionCode];

        return confirmTransaction(_transactionCode, transaction);
    }

    function revoke(uint256 _transactionCode) external
        onlyWallet
        onlyEligible(_transactionCode)
        returns (bool)
    {
        require(TRANSACTION_STATUS_PENDING == transactions[_transactionCode].status, "Root.revoke: Transaction has been completed");
        transactions[_transactionCode].status = TRANSACTION_STATUS_REVOKED;

        emit Revocation(msg.sender, _transactionCode);
        return true;
    }

    function confirmTransaction(uint256 _transactionCode, Transaction storage _transaction) private
        returns (bool)
    {
        TransactionRequirement storage requirement = requirements[_transactionCode][walletToLabelCodes[msg.sender]];
        require(0 != requirement.flag && 0 != requirement.required, "Root.confirmTransaction: Requirement is empty");

        // Prevent duplicated confirmation
        require(!confirmations[_transactionCode][msg.sender], "root.confirmTransaction: Duplicated confirmation");
        confirmations[_transactionCode][msg.sender] = true;

        requirement.required = requirement.required.sub(1);
        _transaction.totalRequired = _transaction.totalRequired.sub(1);

        emit Confirmation(msg.sender, _transactionCode);

        return executeTransaction(_transactionCode, _transaction);
    }

    function executeTransaction(uint256 _transactionCode, Transaction storage _transaction) private
        returns (bool)
    {
        require(TRANSACTION_STATUS_PENDING == _transaction.status, "Root.executeTransaction: Status not active");

        if (0 == _transaction.totalRequired) {
            _transaction.status = TRANSACTION_STATUS_EXECUTED;

            // Perform remote call
            (bool success, bytes memory returnData) = _transaction.to.call.value(_transaction.value)(_transaction.data);

            if (success)
                emit Execution(_transactionCode);
            else {
                // Check the error string is available or not
                if (0 == returnData.length || bytes4(0x08c379a0) != convertBytesToBytes4(returnData))
                    _transaction.reason = "Root.executeTransaction: Function call failed";
                else {
                    bytes memory bytesArray = new bytes(returnData.length);
                    for (uint256 i = 0; i < returnData.length.sub(4); i = i.add(1)) {
                        bytesArray[i] = returnData[i.add(4)];
                    }

                    (string memory reason) = abi.decode(bytesArray, (string));
                    _transaction.reason = reason;
                }

                _transaction.status = TRANSACTION_STATUS_FAILURE;
                emit ExecutionFailure(_transactionCode);
            }

            return success;
        }

        return true;
    }

    function setRegulator(address _addressOf) external
    {
        if (address(0) != regulator)
            require(msg.sender == address(this), "Root.setRegulator: Access denied");
        
        require(MultiSigRegulator(_addressOf).isSealed(), "Root.setRegulator: Regulator is not sealed");

        regulator = setNode(regulator, _addressOf, false);
        emit RegulatorChanged(regulator);
    }

    function setStakers(address _addressOf) external
    {
        if (address(0) != stakers)
            require(msg.sender == address(this), "Root.setStakers: Access denied");

        if (isContract(_addressOf))
            require(0 < MultiSigNode(_addressOf).walletCount(), "Root.setStakers: No wallet");

        stakers = setNode(stakers, _addressOf, true);
        emit StakersChanged(stakers);
    }

    function setSigners(address _addressOf) external
        returns (bool)
    {
        if (address(0) != signers)
            require(msg.sender == address(this), "Root.setSigners: Access denied");

        // Signer node allowed to be empty

        signers = setNode(signers, _addressOf, true);
        emit SignersChanged(signers);
        return true;
    }

    function setStakersController(address _addressOf) external
    {
        if (address(0) != stakersController)
            require(msg.sender == address(this), "Root.setStakersController: Access denied");

        if (isContract(_addressOf))
            require(0 < MultiSigNode(_addressOf).walletCount(), "Root.setStakersController: No wallet");

        stakersController = setNode(stakersController, _addressOf, true);
        emit StakersControllerChanged(stakersController);
    }

    function setSignersController(address _addressOf) external
    {
        if (address(0) != signersController)
            require(msg.sender == address(this), "Root.setSignersController: Access denied");

        if (isContract(_addressOf))
            require(0 < MultiSigNode(_addressOf).walletCount(), "Root.setSignersController: No wallet");

        signersController = setNode(signersController, _addressOf, true);
        emit SignersControllerChanged(signersController);
    }

    function setNode(address _from, address _to, bool needAttachment) private
        returns (address)
    {
        require(address(0) != _to, "Root.setNode: Address is empty");

        if (needAttachment) {
            require(0 == registered[_to], "Root.setNode: Duplicated node");

            // Remove node from list
            if (address(0) != _from) {
                if (isContract(_from)) {
                    // detach node
                    MultiSigNode(_from).term();
                }

                delete registered[_from];
            }

            if (isContract(_to)) {
                // Mark address as registered node
                registered[_to] = WALLET_TYPE_NODE;

                if (needAttachment) {
                    // Attach node (parrent address = 0x0)
                    MultiSigNode(_to).init(address(0));
                }
            }
            else {
                // Mark address as registered wallet
                registered[_to] = WALLET_TYPE_WALLET;
            }
        }

        return _to;
    }

    function attachWalletOrNode(address _wallet) external
        onlyNode
        returns (bool)
    {
        require(address(0) != _wallet, "Root.attachWalletOrNode: Wallet address is empty");
        require(0 == registered[_wallet], "Root.attachWalletOrNode: Duplicated wallet address");

        bytes32 labelCode = MultiSigNode(msg.sender).labelCode();

        walletToNodes[_wallet] = msg.sender;
        walletToIndexes[_wallet] = MultiSigNode(msg.sender).walletsIndex(_wallet);
        walletToLabelCodes[_wallet] = labelCode;

        labelCodeToNodes[labelCode] = msg.sender;

        if (isContract(_wallet)) {
            // Mark address as registered node
            registered[_wallet] = WALLET_TYPE_NODE;

            // Attach node with their parent address
            MultiSigNode(_wallet).init(msg.sender);
        }
        else {
            // Mark address as registered wallet
            registered[_wallet] = WALLET_TYPE_WALLET;
        }

        emit WalletOrNodeAttached(_wallet);

        return true;
    }

    function detachWalletOrNode(address _wallet) external
        onlyNode
        returns (bool)
    {
        require(address(0) != _wallet, "Root.detachWalletOrNode: Wallet address is empty");
        require(0 != registered[_wallet], "Root.detachWalletOrNode: Wallet address is not registered");

        if (isContract(_wallet)) {
            // Detach node with their parent
            MultiSigNode(_wallet).term();

            bytes32 labelCode = MultiSigNode(msg.sender).labelCode();

            delete labelCodeToNodes[labelCode];
        }

        delete registered[_wallet];
        delete walletToNodes[_wallet];
        delete walletToIndexes[_wallet];
        delete walletToLabelCodes[_wallet];

        emit WalletOrNodeDetached(_wallet);

        return true;
    }

}