contract UpgradeProxy is SharedBetweenProxyAndLogic {
    constructor(address _owner) public {
        owner = _owner;
        newOwner = address(0);
        proxyState = ProxyState.Paused;
        logicContract = address(0);
        oldLogicContract = address(0);
        isProxy = true;
        version = 0;
        emit ProxyPaused();
    }

    function proxyTransferOwnership(address _newOwner) external onlyOwner {
        newOwner = _newOwner;
    }

    function proxyAcceptOwnership() external {
        require(msg.sender == newOwner);
        emit OwnershipTransferred(owner, newOwner);
        owner = newOwner;
        newOwner = address(0);
    }

    function proxyPause() external onlyOwner {
        emit ProxyPaused();
        require(proxyState == ProxyState.Normal);
        proxyState = ProxyState.Paused;
    }

    function proxyUnpause() external onlyOwner {
        emit ProxyUnpaused();
        // Make sure the first unpause only happens after a logic contract was set
        require(Utils.isContract(logicContract));
        require(proxyState == ProxyState.Paused);
        proxyState = ProxyState.Normal;
    }

    function proxyStartUpgrade(address newLogicContract, uint256 newVersion)
        external onlyOwner
    {
        require(proxyState == ProxyState.Paused);
        require(version < newVersion);
        require(Utils.isContract(newLogicContract));

        upgradingFromVersion = version;
        upgradingToVersion = newVersion;
        oldLogicContract = logicContract;
        logicContract = newLogicContract;

        emit ProxyStartUpgrade(oldLogicContract, newLogicContract,
                               upgradingFromVersion, upgradingToVersion);
        proxyState = ProxyState.Upgrading;
    }

    function proxyCallMigration(bytes calldata data)
        external onlyOwner
    {
        require(proxyState == ProxyState.Upgrading);
        require(Utils.isContract(logicContract));

        emit ProxyCallMigration(oldLogicContract, logicContract,
                                upgradingFromVersion, upgradingToVersion);

        proxyState = ProxyState.CallingMigrationFunction;
        (bool result,) = logicContract.delegatecall(data);
        require(result);

        require(proxyState == ProxyState.Upgrading);
    }

    function proxyForceVersionBump()
        external onlyOwner
    {
        require(proxyState == ProxyState.Upgrading);
        emit ProxyForceVersionBump(oldLogicContract, logicContract,
                                   upgradingFromVersion, upgradingToVersion);
        version = upgradingToVersion;
    }

    function proxyFinishUpgrade()
        external onlyOwner
    {
        require(proxyState == ProxyState.Upgrading);
        require(version == upgradingToVersion);

        emit ProxyFinishUpgrade(oldLogicContract, logicContract,
                                upgradingFromVersion, upgradingToVersion);
        proxyState = ProxyState.Paused;

        // Clean up storage
        upgradingFromVersion = 0;
        upgradingToVersion = 0;
        oldLogicContract = address(0);
    }

    function proxyCancelUpgrade()
        external onlyOwner
    {
        require(proxyState == ProxyState.Upgrading);

        emit ProxyCancelUpgrade(oldLogicContract, logicContract,
                                upgradingFromVersion, upgradingToVersion);
        logicContract = oldLogicContract;

        // It would be safer to require(version == upgradingFromVersion), but
        // we give us a little bit of freedom here in case we really mess up badly.
        version = upgradingFromVersion;
        proxyState = ProxyState.Paused;

        // Clean up storage
        upgradingFromVersion = 0;
        upgradingToVersion = 0;
        oldLogicContract = address(0);
    }

    // from
    // https://github.com/zeppelinos/labs/blob/master/upgradeability_using_eternal_storage/contracts/Proxy.sol
    function () payable external {
        require(proxyState == ProxyState.Normal);

        // TODO check that the receiver has code
        // https://blog.trailofbits.com/2018/09/05/contract-upgrade-anti-patterns/
        // For now we don't do this for performance reasons
        //require(Utils.isContract(logicContract));
        address impl = logicContract;

        assembly {
            let ptr := mload(0x40)
            calldatacopy(ptr, 0, calldatasize)
            let result := delegatecall(gas, impl, ptr, calldatasize, 0, 0)
            let size := returndatasize
            returndatacopy(ptr, 0, size)

            switch result
            case 0 { revert(ptr, size) }
            default { return(ptr, size) }
        }
    }
}
function () payable external {
    require(proxyState == ProxyState.Normal);

    // TODO check that the receiver has code
    // https://blog.trailofbits.com/2018/09/05/contract-upgrade-anti-patterns/
    // For now we don't do this for performance reasons
    //require(Utils.isContract(logicContract));
    address impl = logicContract;

    assembly {
        let ptr := mload(0x40)
        calldatacopy(ptr, 0, calldatasize)
        let result := delegatecall(gas, impl, ptr, calldatasize, 0, 0)
        let size := returndatasize
        returndatacopy(ptr, 0, size)

        switch result
        case 0 { revert(ptr, size) }
        default { return(ptr, size) }
    }
}

// from
// https://github.com/zeppelinos/labs/blob/master/upgradeability_using_eternal_storage/contracts/Proxy.sol