contract WrapperLockDai is ERC20, ERC20Detailed, Ownable, DSMath {
    using SafeERC20 for IERC20;
    using SafeMath for uint256;

    address public TRANSFER_PROXY_V2 = 0x95E6F48254609A6ee006F7D493c8e5fB97094ceF;
    address public daiJoin;
    address public pot;
    mapping (address => uint256) public pieBalance; 
    uint public Pie;
    mapping (address => bool) public isSigner;

    address public originalToken;
    uint public interestFee;
    
    mapping (address => uint) public depositLock;

    uint constant public MAX_PERCENTAGE = 100;
    uint constant WAD_TO_RAY = 10 ** 9;

    event InterestFeeSet(uint interestFee);
    event Withdraw(uint pie, uint exitWad);
    event Test(address account, uint amount);

    constructor (address _originalToken, string memory name, string memory symbol, uint8 decimals, uint _interestFee, address _daiJoin, address _daiPot) public Ownable() ERC20Detailed(name, symbol, decimals) {
        require(_interestFee >= 0 && _interestFee <= MAX_PERCENTAGE);

        originalToken = _originalToken;
        interestFee = _interestFee;
        daiJoin = _daiJoin;
        pot = _daiPot;
        isSigner[msg.sender] = true;

        emit InterestFeeSet(interestFee);
    }

    function _mintPie(address account, uint pie) internal {
        pieBalance[account] = add(pieBalance[account], pie);
        Pie = add(Pie, pie);
    }

    function _burnPie(address account, uint pie) internal {
        pieBalance[account] = sub(pieBalance[account], pie);
        Pie = sub(Pie, pie);
    }
    
    /*
    // @dev method only for testing, needs to be commented out when deploying
    function addProxy(address _addr) public {
        TRANSFER_PROXY_V2 = _addr;
    }
    */

    // @dev Transfer original token from the user, deposit them in DSR to get interest, and give the user wrapped tokens
    function deposit(uint _value, uint _forTime) public returns (bool success) {
        require(_forTime >= 1);
        require(now + _forTime * 1 hours >= depositLock[msg.sender]);
        IERC20(originalToken).safeTransferFrom(msg.sender, address(this), _value);

        DaiJoinLike(daiJoin).dai().approve(daiJoin, _value);
        DaiJoinLike(daiJoin).join(address(this), _value);

        uint pie = _joinPot(_value);
        
        _mint(msg.sender, _value);
        _mintPie(msg.sender, pie);
        depositLock[msg.sender] = now + _forTime * 1 hours;
        return true;
    }

    // @dev Send WRAP to withdraw DAI tokens, recieving the corresponding interest for that part of the user's deposited amount in DAI.
    function withdraw(
        uint _value,
        uint8 v,
        bytes32 r,
        bytes32 s,
        uint signatureValidUntilBlock
    )
        public
        returns
        (bool success)
    {
        if (now > depositLock[msg.sender]) {
            
        } else {
        require(block.number < signatureValidUntilBlock);
        require(isValidSignature(keccak256(abi.encodePacked(msg.sender, address(this), signatureValidUntilBlock)), v, r, s), "signature");
            
        depositLock[msg.sender] = 0;
        }
        uint pie = _getPiePercentage(msg.sender, _value); // [Precision?] Division operation 
        uint exitWad = _exitPot(pie); // [Precision? - SF conversion: Wrapper can gain VAT Dust

        uint userInterest = _getInterestSplit(_value, exitWad);
        DaiJoinLike(daiJoin).exit(msg.sender, add(_value, userInterest)); // Convert principal + userInterest to DAI + send to user

        _burn(msg.sender, _value);
        _burnPie(msg.sender, pie);
        
        emit Withdraw(pie, exitWad); 
        return true;
    }

    // @dev Calculate the value of users' normalized balance that proportionally corresponds to denormalized amount
    function _getPiePercentage(address account, uint amount) public returns (uint) {
        require(amount > 0);
        require(balanceOf(account) > 0);
        require(pieBalance[account] > 0);

        if (amount == balanceOf(account)) {
            return pieBalance[account];
        }

        uint rpercentage = rdiv(mul(amount, WAD_TO_RAY), mul(balanceOf(account), WAD_TO_RAY));
        uint pie = rmul(mul(pieBalance[account], WAD_TO_RAY), rpercentage) / WAD_TO_RAY;
        return pie;
    }

    // @dev Admin function to 'gulp' excess tokens that were sent to this address, for the wrapped token
    // @dev The wrapped token doesn't store balances anymore - that DAI is sent from the user to the proxy, converted to vat balance (burned in the process), and deposited in the pot on behalf of the proxy.
    // @dev So, we can safely assume any dai tokens sent here are withdrawable.
    function withdrawBalanceDifference() public onlyOwner returns (bool success) {
        uint bal = IERC20(originalToken).balanceOf(address(this));
        require (bal > 0);
        IERC20(originalToken).safeTransfer(msg.sender, bal);
        return true;
    }

    // @dev Admin function to 'gulp' excess tokens that were sent to this address, for any token other than the wrapped
    function withdrawDifferentToken(address _differentToken) public onlyOwner returns (bool) {
        require(_differentToken != originalToken);
        require(IERC20(_differentToken).balanceOf(address(this)) > 0);
        IERC20(_differentToken).safeTransfer(msg.sender, IERC20(_differentToken).balanceOf(address(this)));
        return true;
    }

    // @dev Admin function to withdraw excess vat balance to Owner
    // @param _rad Balance to withdraw, in Rads
    function withdrawVatBalance(uint _rad) public onlyOwner returns (bool) {
        DaiJoinLike(daiJoin).vat().move(address(this), owner(), _rad);
    }

    // @dev Pie can accumulate in the pot when we transferFrom without resolving interest. This allows for the exchange to collect small interest amounts in their entirety, 
    function exitExcessPie() public onlyOwner returns (bool) {
        uint truePie = PotLike(pot).pie(address(this));
        uint excessPie = sub(truePie, Pie);

        _exitPot(excessPie);
    }

    // @dev Admin function to change interestFee for future calculations
    function setInterestFee(uint _interestFee) public onlyOwner returns (bool) {
        require(_interestFee >= 0 && _interestFee <= MAX_PERCENTAGE);

        interestFee = _interestFee;
        emit InterestFeeSet(interestFee);
    }

    // @dev Override from ERC20 - We don't allow the users to transfer their wrapped tokens directly
    function transfer(address _to, uint256 _value) public returns (bool) {
        return false;
    }

    // @dev Get interest user is entitled to, based on current interestFee
    // @dev The remaining interest stays in the exhcange as VAT, to be withdrawn or converted at-will
    function _getInterestSplit(uint principal, uint plusInterest) internal returns(uint) {
        if (plusInterest <= principal) {
            return 0;
        }

        uint interest = sub(plusInterest, principal);

        if (interestFee == 0) {
            return interest;
        }

        if (interestFee == MAX_PERCENTAGE) {
            return 0;
        }

        // [Precision] Take rounding error for exchange [If we want to give the user the rounding error, calculate exchangeInterest first using interestFee]
        uint userInterestPercentage = sub(MAX_PERCENTAGE, interestFee);
        uint userInterest = mul(interest, userInterestPercentage) / MAX_PERCENTAGE;
        return userInterest;
    }

    // @dev Override from  ERC20: We don't allow the users to transfer their wrapped tokens directly
    // @dev The tokens must be transffered to or from a signer, not between normal users
    // @dev ONLY the TransferProxy can call this - a safeguard as it already has the exclusive right to be given allowances.
    // @param _from as per ERC20
    // @param _to as per ERC20
    // @param _value as per ERC20
    // // @param _resolveInterest 
    function transferFrom(address _from, address _to, uint _value) public returns (bool) {
        require(isSigner[_to] || isSigner[_from]);
        assert(msg.sender == TRANSFER_PROXY_V2);
        depositLock[_to] = depositLock[_to] > now ? depositLock[_to] : now + 1 hours;
        // Take the corresponding pie from the pot & reduce sender Pie accordingly.
        uint pie = _getPiePercentage(_from, _value);
        _burnPie(_from, pie);

        _transfer(_from, _to, _value); //Handles cases of 0 from balance or amount

        // if (_resolveInterest) {
            uint exitWad = _exitPot(pie);

            // Track & reinvest interest gained, if any
            uint userInterest = _getInterestSplit(_value, exitWad);

            if (userInterest > 0) {
                // Mint new WRAP tokens for the user
                // Remaining VAT will stay in the exchange [We don't want to do this conversion now for gas reasons]

                uint interestPie = _joinPot(userInterest);
                _mint(_from, userInterest);
                _mintPie(_from, interestPie);
            }

            // Use the pie value (for the amount transferred) and deposit on behalf of B. It will be worth a different Pie value than it was originally.
            uint toPie = _joinPot(_value);
            _mintPie(_to, toPie);
        // }
    }

    // @dev Allowancss can only be set with the TransferProxy as the spender, meaning only it can use transferFrom
    function allowance(address _owner, address _spender) public view returns (uint) {
        if (_spender == TRANSFER_PROXY_V2) {
            return 2**256 - 1;
        }
    }

    function isValidSignature(
        bytes32 hash,
        uint8 v,
        bytes32 r,
        bytes32 s
    )
        public
        view
        returns (bool)
    {
        return isSigner[ecrecover(
            keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash)),
            v,
            r,
            s
        )];
        
    }

    // @dev Existing signers can add new signers
    function addSigner(address _newSigner) public {
        require(isSigner[msg.sender]);
        isSigner[_newSigner] = true;
    }

    function keccak(address _sender, address _wrapper, uint _validTill) public pure returns(bytes32) {
        return keccak256(abi.encodePacked(_sender, _wrapper, _validTill));
    }

    // @dev wad is denominated in dai
    function _joinPot(uint wad) internal returns (uint) {
         VatLike vat = DaiJoinLike(daiJoin).vat();

        // Executes drip to get the chi rate if needed, otherwise join will fail
        uint chi = PotLike(pot).drip();

        // [Precision Loss]: Scaled Mul, Ray -> Wad scale
        uint pie = mul(wad, RAY) / chi;

        // Approves the pot to take out DAI from the proxy's balance in the vat
        if (vat.can(address(this), address(pot)) == 0) {
            vat.hope(pot);
        }

        // Joins the pie value (equivalent to the DAI wad amount) in the` pot
        PotLike(pot).join(pie);
        return pie;
    }

    // wad is denominated in (1/chi) * dai
    function _exitPot(uint pie) internal returns (uint) {
        VatLike vat = DaiJoinLike(daiJoin).vat();

        // Executes drip to get the chi rate if needed, otherwise join will fail
        uint chi = PotLike(pot).drip();
        uint expectedWad = mul(pie, chi) / RAY;
        PotLike(pot).exit(pie);

        // Checks the actual balance of DAI in the vat after the pot exit
        // [ Rounding ]
        uint bal = DaiJoinLike(daiJoin).vat().dai(address(this));

        // Allows adapter to access to proxy's DAI balance in the vat
        if (vat.can(address(this), address(daiJoin)) == 0) {
            vat.hope(daiJoin);
        }

        /* [Proxy] It is necessary to check if due rounding the exact wad amount can be exited by the adapter. Otherwise it will do the maximum DAI balance in the vat 
            [Dev] This is because it's possible to receive less than what we're entitled to on any given operation - but by how much?
        */
        uint exitWad = bal >= mul(expectedWad, RAY) ? expectedWad : bal / RAY;
        return exitWad;
    }
}
function withdraw(
        uint _value,
        uint8 v,
        bytes32 r,
        bytes32 s,
        uint signatureValidUntilBlock
    )
        public
        returns
        (bool success)
    {
        if (now > depositLock[msg.sender]) {
            
        } else {
        require(block.number < signatureValidUntilBlock);
        require(isValidSignature(keccak256(abi.encodePacked(msg.sender, address(this), signatureValidUntilBlock)), v, r, s), "signature");
            
        depositLock[msg.sender] = 0;
        }
        uint pie = _getPiePercentage(msg.sender, _value); // [Precision?] Division operation 
        uint exitWad = _exitPot(pie); // [Precision? - SF conversion: Wrapper can gain VAT Dust

        uint userInterest = _getInterestSplit(_value, exitWad);
        DaiJoinLike(daiJoin).exit(msg.sender, add(_value, userInterest)); // Convert principal + userInterest to DAI + send to user

        _burn(msg.sender, _value);
        _burnPie(msg.sender, pie);
        
        emit Withdraw(pie, exitWad); 
        return true;
    }
// @dev Send WRAP to withdraw DAI tokens, recieving the corresponding interest for that part of the user's deposited amount in DAI.