pragma solidity 0.6.12;
pragma experimental ABIEncoderV2;

interface ERC20 {

    function totalSupply() external view returns (uint);

    function decimals() external view returns (uint);

    function balanceOf(address tokenOwner) external view returns (uint balance);

    function allowance(address tokenOwner, address spender) external view returns (uint remaining);

    function transfer(address to, uint tokens) external returns (bool success);

    function approve(address spender, uint tokens) external returns (bool success);

    function transferFrom(address from, address to, uint tokens) external returns (bool success);

}

interface IFeature {



    enum OwnerSignature {

        Anyone,             

        Required,           

        Optional,           

        Disallowed          

    }



    

    function recoverToken(address _token) external;



    

    function init(address _wallet) external;



    

    function isFeatureAuthorisedInVersionManager(address _wallet, address _feature) external view returns (bool);



    

    function getRequiredSignatures(address _wallet, bytes calldata _data) external view returns (uint256, OwnerSignature);



    

    function getStaticCallSignatures() external view returns (bytes4[] memory);

}

interface IGuardianStorage {



    

    function addGuardian(address _wallet, address _guardian) external;



    

    function revokeGuardian(address _wallet, address _guardian) external;



    

    function isGuardian(address _wallet, address _guardian) external view returns (bool);



    function isLocked(address _wallet) external view returns (bool);



    function getLock(address _wallet) external view returns (uint256);



    function getLocker(address _wallet) external view returns (address);



    function setLock(address _wallet, uint256 _releaseAfter) external;



    function getGuardians(address _wallet) external view returns (address[] memory);



    function guardianCount(address _wallet) external view returns (uint256);

}

interface ILimitStorage {



    struct Limit {

        

        uint128 current;

        

        uint128 pending;

        

        uint64 changeAfter;

    }



    struct DailySpent {

        

        uint128 alreadySpent;

        

        uint64 periodEnd;

    }



    function setLimit(address _wallet, Limit memory _limit) external;



    function getLimit(address _wallet) external view returns (Limit memory _limit);



    function setDailySpent(address _wallet, DailySpent memory _dailySpent) external;



    function getDailySpent(address _wallet) external view returns (DailySpent memory _dailySpent);



    function setLimitAndDailySpent(address _wallet, Limit memory _limit, DailySpent memory _dailySpent) external;



    function getLimitAndDailySpent(address _wallet) external view returns (Limit memory _limit, DailySpent memory _dailySpent);

}

interface ILockStorage {

    function isLocked(address _wallet) external view returns (bool);



    function getLock(address _wallet) external view returns (uint256);



    function getLocker(address _wallet) external view returns (address);



    function setLock(address _wallet, address _locker, uint256 _releaseAfter) external;

}

interface IModule {

    

    function init(address _wallet) external;



    	

    function addModule(address _wallet, address _module) external;

}

interface IModuleRegistry {

    function registerModule(address _module, bytes32 _name) external;



    function deregisterModule(address _module) external;



    function registerUpgrader(address _upgrader, bytes32 _name) external;



    function deregisterUpgrader(address _upgrader) external;



    function recoverToken(address _token) external;



    function moduleInfo(address _module) external view returns (bytes32);



    function upgraderInfo(address _upgrader) external view returns (bytes32);



    function isRegisteredModule(address _module) external view returns (bool);



    function isRegisteredModule(address[] calldata _modules) external view returns (bool);



    function isRegisteredUpgrader(address _upgrader) external view returns (bool);

}

interface ITransferStorage {

    function setWhitelist(address _wallet, address _target, uint256 _value) external;



    function getWhitelist(address _wallet, address _target) external view returns (uint256);

}

interface IVersionManager {

    

    function isFeatureAuthorised(address _wallet, address _feature) external view returns (bool);



    

    function checkAuthorisedFeatureAndInvokeWallet(

        address _wallet,

        address _to,

        uint256 _value,

        bytes calldata _data

    ) external returns (bytes memory _res);



    



    

    function setOwner(address _wallet, address _newOwner) external;



    

    function invokeStorage(address _wallet, address _storage, bytes calldata _data) external;



    

    function upgradeWallet(address _wallet, uint256 _toVersion) external;

 

}

interface IWallet {

    

    function owner() external view returns (address);



    

    function modules() external view returns (uint);



    

    function setOwner(address _newOwner) external;



    

    function authorised(address _module) external view returns (bool);



    

    function enabled(bytes4 _sig) external view returns (address);



    

    function authoriseModule(address _module, bool _value) external;



    

    function enableStaticCall(address _module, bytes4 _method) external;

}

contract Owned {



    

    address public owner;



    event OwnerChanged(address indexed _newOwner);



    

    modifier onlyOwner {

        require(msg.sender == owner, "Must be owner");

        _;

    }



    constructor() public {

        owner = msg.sender;

    }



    

    function changeOwner(address _newOwner) external onlyOwner {

        require(_newOwner != address(0), "Address must not be null");

        owner = _newOwner;

        emit OwnerChanged(_newOwner);

    }

}

library SafeMath {

    

    function add(uint256 a, uint256 b) internal pure returns (uint256) {

        uint256 c = a + b;

        require(c >= a, "SafeMath: addition overflow");



        return c;

    }



    

    function sub(uint256 a, uint256 b) internal pure returns (uint256) {

        return sub(a, b, "SafeMath: subtraction overflow");

    }



    

    function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {

        require(b <= a, errorMessage);

        uint256 c = a - b;



        return c;

    }



    

    function mul(uint256 a, uint256 b) internal pure returns (uint256) {

        

        

        

        if (a == 0) {

            return 0;

        }



        uint256 c = a * b;

        require(c / a == b, "SafeMath: multiplication overflow");



        return c;

    }



    

    function div(uint256 a, uint256 b) internal pure returns (uint256) {

        return div(a, b, "SafeMath: division by zero");

    }



    

    function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {

        

        require(b > 0, errorMessage);

        uint256 c = a / b;

        



        return c;

    }



    

    function mod(uint256 a, uint256 b) internal pure returns (uint256) {

        return mod(a, b, "SafeMath: modulo by zero");

    }



    

    function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {

        require(b != 0, errorMessage);

        return a % b;

    }

}

library Utils {



    

    function recoverSigner(bytes32 _signedHash, bytes memory _signatures, uint _index) internal pure returns (address) {

        uint8 v;

        bytes32 r;

        bytes32 s;

        

        

        

        

        assembly {

            r := mload(add(_signatures, add(0x20,mul(0x41,_index))))

            s := mload(add(_signatures, add(0x40,mul(0x41,_index))))

            v := and(mload(add(_signatures, add(0x41,mul(0x41,_index)))), 0xff)

        }

        require(v == 27 || v == 28);



        address recoveredAddress = ecrecover(_signedHash, v, r, s);

        require(recoveredAddress != address(0), "Utils: ecrecover returned 0");

        return recoveredAddress;

    }



    

    function functionPrefix(bytes memory _data) internal pure returns (bytes4 prefix) {

        require(_data.length >= 4, "RM: Invalid functionPrefix");

        

        assembly {

            prefix := mload(add(_data, 0x20))

        }

    }



    

    function ceil(uint256 a, uint256 b) internal pure returns (uint256) {

        uint256 c = a / b;

        if (a % b == 0) {

            return c;

        } else {

            return c + 1;

        }

    }



    function min(uint256 a, uint256 b) internal pure returns (uint256) {

        if (a < b) {

            return a;

        }

        return b;

    }

}

contract BaseFeature is IFeature {



    

    bytes constant internal EMPTY_BYTES = "";

    

    address constant internal ETH_TOKEN = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;

    

    ILockStorage internal lockStorage;

    

    IVersionManager internal versionManager;



    event FeatureCreated(bytes32 name);



    

    modifier onlyWhenUnlocked(address _wallet) {

        require(!lockStorage.isLocked(_wallet), "BF: wallet locked");

        _;

    }



    

    modifier onlyVersionManager() {

        require(msg.sender == address(versionManager), "BF: caller must be VersionManager");

        _;

    }



    

    modifier onlyWalletOwner(address _wallet) {

        require(isOwner(_wallet, msg.sender), "BF: must be wallet owner");

        _;

    }



    

    modifier onlyWalletFeature(address _wallet) {

        require(versionManager.isFeatureAuthorised(_wallet, msg.sender), "BF: must be a wallet feature");

        _;

    }



    

    modifier onlyWalletOwnerOrFeature(address _wallet) {

        

        verifyOwnerOrAuthorisedFeature(_wallet, msg.sender);

        _;

    }



    constructor(

        ILockStorage _lockStorage,

        IVersionManager _versionManager,

        bytes32 _name

    ) public {

        lockStorage = _lockStorage;

        versionManager = _versionManager;

        emit FeatureCreated(_name);

    }



    

    function recoverToken(address _token) external virtual override {

        uint total = ERC20(_token).balanceOf(address(this));

        _token.call(abi.encodeWithSelector(ERC20(_token).transfer.selector, address(versionManager), total));

    }



    

    function init(address _wallet) external virtual override  {}



    

    function getRequiredSignatures(address, bytes calldata) external virtual view override returns (uint256, OwnerSignature) {

        revert("BF: disabled method");

    }



    

    function getStaticCallSignatures() external virtual override view returns (bytes4[] memory _sigs) {}



    

    function isFeatureAuthorisedInVersionManager(address _wallet, address _feature) public override view returns (bool) {

        return versionManager.isFeatureAuthorised(_wallet, _feature);

    }



    

    function verifyData(address _wallet, bytes calldata _data) internal pure returns (bool) {

        require(_data.length >= 36, "RM: Invalid dataWallet");

        address dataWallet = abi.decode(_data[4:], (address));

        return dataWallet == _wallet;

    }

    

     

    function isOwner(address _wallet, address _addr) internal view returns (bool) {

        return IWallet(_wallet).owner() == _addr;

    }



    

    function verifyOwnerOrAuthorisedFeature(address _wallet, address _sender) internal view {

        require(isFeatureAuthorisedInVersionManager(_wallet, _sender) || isOwner(_wallet, _sender), "BF: must be owner or feature");

    }



    

    function invokeWallet(address _wallet, address _to, uint256 _value, bytes memory _data)

        internal

        returns (bytes memory _res) 

    {

        _res = versionManager.checkAuthorisedFeatureAndInvokeWallet(_wallet, _to, _value, _data);

    }



}

contract VersionManager is IVersionManager, IModule, BaseFeature, Owned {



    bytes32 constant NAME = "VersionManager";



    bytes4 constant internal ADD_MODULE_PREFIX = bytes4(keccak256("addModule(address,address)"));

    bytes4 constant internal UPGRADE_WALLET_PREFIX = bytes4(keccak256("upgradeWallet(address,uint256)"));



    

    uint256 public lastVersion;

    

    uint256 public minVersion = 1;

    

    mapping(address => uint256) public walletVersions; 

    

    mapping(address => mapping(uint256 => bool)) public isFeatureInVersion; 

    

    mapping(uint256 => address[]) public featuresToInit; 



    

    mapping(uint256 => bytes4[]) public staticCallSignatures; 

    

    mapping(uint256 => mapping(bytes4 => address)) public staticCallExecutors; 



    

    mapping(address => bool) public isStorage; 



    event VersionAdded(uint256 _version, address[] _features);

    event WalletUpgraded(address indexed _wallet, uint256 _version);



    

    IModuleRegistry private registry;



    



    constructor(

        IModuleRegistry _registry,

        ILockStorage _lockStorage,

        IGuardianStorage _guardianStorage,

        ITransferStorage _transferStorage,

        ILimitStorage _limitStorage

    )

        BaseFeature(_lockStorage, IVersionManager(address(this)), NAME)

        public

    {

        registry = _registry;



        

        if(address(_lockStorage) != address(0)) { 

            addStorage(address(_lockStorage));

        }

        if(address(_guardianStorage) != address(0)) { 

            addStorage(address(_guardianStorage));

        }

        if(address(_transferStorage) != address(0)) {

            addStorage(address(_transferStorage));

        }

        if(address(_limitStorage) != address(0)) {

            addStorage(address(_limitStorage));

        }

    }



    



    

    function recoverToken(address _token) external override onlyOwner {

        uint total = ERC20(_token).balanceOf(address(this));

        _token.call(abi.encodeWithSelector(ERC20(_token).transfer.selector, msg.sender, total));

    }



    

    function setMinVersion(uint256 _minVersion) external onlyOwner {

        require(_minVersion > 0 && _minVersion <= lastVersion, "VM: invalid _minVersion");

        minVersion = _minVersion;

    }



    

    function addVersion(address[] calldata _features, address[] calldata _featuresToInit) external onlyOwner {

        uint256 newVersion = ++lastVersion;

        for(uint256 i = 0; i < _features.length; i++) {

            isFeatureInVersion[_features[i]][newVersion] = true;



            

            bytes4[] memory sigs = IFeature(_features[i]).getStaticCallSignatures();

            for(uint256 j = 0; j < sigs.length; j++) {

                staticCallSignatures[newVersion].push(sigs[j]);

                staticCallExecutors[newVersion][sigs[j]] = _features[i];

            }

        }



        

        for(uint256 i = 0; i < _featuresToInit.length; i++) {

            require(isFeatureInVersion[_featuresToInit[i]][newVersion], "VM: invalid _featuresToInit");

        }



        featuresToInit[newVersion] = _featuresToInit;

        

        emit VersionAdded(newVersion, _features);

    }

   

    

    function addStorage(address _storage) public onlyOwner {

        require(!isStorage[_storage], "VM: storage already added");

        isStorage[_storage] = true;

    }



    



    

    function isFeatureAuthorised(address _wallet, address _feature) external view override returns (bool) {

        

        return _isFeatureAuthorisedForWallet(_wallet, _feature) || _feature == address(this);

    }



    

    function getRequiredSignatures(address , bytes calldata _data) external view override returns (uint256, OwnerSignature) {

        bytes4 methodId = Utils.functionPrefix(_data);

        

        

        require(methodId == UPGRADE_WALLET_PREFIX || methodId == ADD_MODULE_PREFIX, "VM: unknown method");     

        return (1, OwnerSignature.Required);

    }



    

    fallback() external {

        uint256 version = walletVersions[msg.sender];

        address feature = staticCallExecutors[version][msg.sig];

        require(feature != address(0), "VM: static call not supported for wallet version");



        

        assembly {

            calldatacopy(0, 0, calldatasize())

            let result := staticcall(gas(), feature, 0, calldatasize(), 0, 0)

            returndatacopy(0, 0, returndatasize())

            switch result

            case 0 {revert(0, returndatasize())}

            default {return (0, returndatasize())}

        }

    }



    



    

    function init(address _wallet) public override(IModule, BaseFeature) {}



    

    function upgradeWallet(address _wallet, uint256 _toVersion) external override onlyWhenUnlocked(_wallet) {

        require(

            

            _isFeatureAuthorisedForWallet(_wallet, msg.sender) ||

            

            IWallet(_wallet).authorised(msg.sender) ||

            

            isOwner(_wallet, msg.sender), 

            "VM: sender may not upgrade wallet"

        );

        uint256 fromVersion = walletVersions[_wallet];

        uint256 minVersion_ = minVersion;

        uint256 toVersion;



        if(_toVersion < minVersion_ && fromVersion == 0 && IWallet(_wallet).modules() == 2) {

            

            

            

            

            toVersion = minVersion_;

        } else {

            toVersion = _toVersion;

        }

        require(toVersion >= minVersion_ && toVersion <= lastVersion, "VM: invalid _toVersion");

        require(fromVersion < toVersion, "VM: already on new version");

        walletVersions[_wallet] = toVersion;



        

        bytes4[] storage sigs = staticCallSignatures[toVersion];

        for(uint256 i = 0; i < sigs.length; i++) {

            bytes4 sig = sigs[i];

            if(IWallet(_wallet).enabled(sig) != address(this)) {

                IWallet(_wallet).enableStaticCall(address(this), sig);

            }

        }

        

        

        address[] storage featuresToInitInToVersion = featuresToInit[toVersion];

        for(uint256 i = 0; i < featuresToInitInToVersion.length; i++) {

            address feature = featuresToInitInToVersion[i];

            

            if(fromVersion == 0 || !isFeatureInVersion[feature][fromVersion]) {

                IFeature(feature).init(_wallet);

            }

        }

        

        emit WalletUpgraded(_wallet, toVersion);



    }



    

    function addModule(address _wallet, address _module) external override onlyWalletOwnerOrFeature(_wallet) onlyWhenUnlocked(_wallet) {

        require(registry.isRegisteredModule(_module), "VM: module is not registered");

        IWallet(_wallet).authoriseModule(_module, true);

    }



    



    

    function checkAuthorisedFeatureAndInvokeWallet(

        address _wallet, 

        address _to, 

        uint256 _value, 

        bytes memory _data

    ) 

        external 

        override

        returns (bytes memory _res) 

    {

        require(_isFeatureAuthorisedForWallet(_wallet, msg.sender), "VM: sender may not invoke wallet");

        bool success;

        (success, _res) = _wallet.call(abi.encodeWithSignature("invoke(address,uint256,bytes)", _to, _value, _data));

        if (success && _res.length > 0) { 

            (_res) = abi.decode(_res, (bytes));

        } else if (_res.length > 0) {

            

            assembly {

                returndatacopy(0, 0, returndatasize())

                revert(0, returndatasize())

            }

        } else if (!success) {

            revert("VM: wallet invoke reverted");

        }

    }



    

    function invokeStorage(address _wallet, address _storage, bytes calldata _data) external override {

        require(_isFeatureAuthorisedForWallet(_wallet, msg.sender), "VM: sender may not invoke storage");

        require(verifyData(_wallet, _data), "VM: target of _data != _wallet");

        require(isStorage[_storage], "VM: invalid storage invoked");

        (bool success,) = _storage.call(_data);

        require(success, "VM: _storage failed");

    }



    

    function setOwner(address _wallet, address _newOwner) external override {

        require(_isFeatureAuthorisedForWallet(_wallet, msg.sender), "VM: sender should be authorized feature");

        IWallet(_wallet).setOwner(_newOwner);

    }



    



    function _isFeatureAuthorisedForWallet(address _wallet, address _feature) private view returns (bool) {

        return isFeatureInVersion[_feature][walletVersions[_wallet]];

    }

}