
pragma solidity 0.8.26;

import {IVaultFactory} from "../interface/IVaultFactory.sol";
import {IVaultProxy,IVaultImpl} from "../interface/IVault.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {Proxy} from "@openzeppelin/contracts/proxy/Proxy.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "./OrderSpec.sol";


// All state in Vault is declared in VaultStorage so it will be identical in VaultImpl

contract VaultStorage is ReentrancyGuard { // The re-entrancy lock is part of the state
    address internal _impl;
    address internal _owner;
    bool internal _killed; // each Vault may be independently killed by its owner at any time
    OrdersInfo internal _ordersInfo;
}

// Vault is implemented in three parts:
// 1. VaultStorage contains the data members, which cannot be upgraded.  The number of data slots is fixed upon
//    construction of the Vault contract.
// 2. Vault itself inherits from the state and adds several "top-level" methods for receiving and withdrawing funds and
//    for upgrading the VaultImpl delegate.  These vault methods may not be changed or upgraded.
// 3. VaultImpl is a deployed contract shared by Vaults.  If a method call is not found on Vault directly, the call
//    is delegated to the contract address stored in the `_impl` variable.  The VaultImpl contract is basically the
//    OrderLib, implementing the common order manipulation methods.  Each Vault may be independently upgraded to point
//    to a new VaultImpl contract by the owner calling their vault's `upgrade()` method with the correct argument.

contract Vault is IVaultProxy, VaultStorage, Proxy {
    //REV putting all variable decls together so we can more easily see visibility and immutability errors
    IVaultFactory immutable private _factory;
    uint8 immutable private _num;

    function implementation() external view override returns(address) {return _impl;}

    function factory() external view override returns(address) {return address(_factory);}

    function killed() external view override returns(bool) {return _killed;}

    function owner() external view override returns(address) {return _owner;}

    function num() external view override returns(uint8) {return _num;}

    // for OpenZeppelin Proxy to call implementation contract via fallback
    function _implementation() internal view override returns (address) {
        // If the VaultFactory that created this vault has had its kill switch activated, do not trust the implementation.
        // local _killed var allows individual users to put their vaults into "killed" mode, where Dexorder functionality
        // is disabled, but funds can still be moved.
        require(!_killed && !_factory.killed(), 'K');
        return _impl;
    }

    constructor() {
        _factory = IVaultFactory(msg.sender);
        (_owner, _num, _impl) = _factory.parameters();
        emit VaultCreated( _owner, _num );
        emit VaultImplChanged(_impl);
    }

    // "Killing" a Vault prevents this proxy from forwarding any calls to the VaultImpl delegate contract. This means
    // all executions are stopped. Orders cannot be placed or canceled.
    function kill() external override onlyOwner {
        emit Killed();
        _killed = true;
    }

    function upgrade(address newImpl) external override onlyOwner {
        // we force the upgrader to explicitly pass in the implementation contract address, then we
        // ensure that it matches the factory's current version.
        require( newImpl == _factory.implementation(), 'UV' );
        address oldImpl = _impl;
        if(oldImpl==newImpl){
            return;
        }
        _impl = newImpl;
        IVaultImpl(address(this)).vaultImplDidChange(oldImpl);
        emit VaultImplChanged(newImpl);
    }

    receive() external payable override {
        emit Deposit(msg.sender, msg.value);
    }

    function withdraw(uint256 amount) external override {
        _withdrawNative(payable(_owner), amount);
    }

    function withdrawTo(address payable recipient, uint256 amount) external override {
        _withdrawNative(recipient, amount);
    }

    function _withdrawNative(address payable recipient, uint256 amount) internal onlyOwner {
        recipient.transfer(amount);
        emit Withdrawal(recipient, msg.value);
    }

    function withdraw(IERC20 token, uint256 amount) external override {
        _withdraw(token, _owner, amount);
    }

    function withdrawTo(IERC20 token, address recipient, uint256 amount) external override {
        _withdraw(token, recipient, amount);
    }

    function _withdraw(IERC20 token, address recipient, uint256 amount) internal onlyOwner nonReentrant {
        token.transfer(recipient, amount);
    }

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

}
