/* Copyright 2020 DODO ZOO. SPDX-License-Identifier: Apache-2.0 */ pragma solidity 0.6.9; pragma experimental ABIEncoderV2; import {InitializableOwnable} from "../../lib/InitializableOwnable.sol"; import {ReentrancyGuard} from "../../lib/ReentrancyGuard.sol"; import {IQuota} from "../../DODOFee/UserQuota.sol"; import {SafeMath} from "../../lib/SafeMath.sol"; import {DecimalMath} from "../../lib/DecimalMath.sol"; import {IERC20} from "../../intf/IERC20.sol"; import {SafeERC20} from "../../lib/SafeERC20.sol"; contract InstantFunding is InitializableOwnable, ReentrancyGuard { using SafeMath for uint256; using SafeERC20 for IERC20; // ============ Token & Balance ============ uint256 public _FUNDS_RESERVE_; address public _FUNDS_ADDRESS_; address public _TOKEN_ADDRESS_; uint256 public _TOTAL_TOKEN_AMOUNT_; // ============ Instant Commit Mode ============ uint256 public _START_TIME_; uint256 public _DURATION_; uint256 public _START_PRICE_; uint256 public _END_PRICE_; address _QUOTA_; mapping(address => uint256) _FUNDS_USED_; mapping(address => uint256) _FUNDS_UNUSED_; mapping(address => uint256) _TOKEN_ALLOCATION_; uint256 public _TOTAL_ALLOCATED_TOKEN_; uint256 public _TOTAL_RAISED_FUNDS_; // ============ View Functions ============ function getCurrentPrice() public view returns (uint256 price) { if (block.timestamp <= _START_TIME_) { price = _START_PRICE_; } else if (block.timestamp >= _START_TIME_.add(_DURATION_)) { price = _END_PRICE_; } else { uint256 timePast = block.timestamp.sub(_START_TIME_); price = _START_PRICE_.mul(timePast).div(_DURATION_).add( _END_PRICE_.mul(_DURATION_.sub(timePast)).div(_DURATION_) ); } } function getUserTokenAllocation(address user) public view returns (uint256) { return _TOKEN_ALLOCATION_[user]; } function getUserFundsUnused(address user) public view returns (uint256) { return _FUNDS_UNUSED_[user]; } function getUserFundsUsed(address user) public view returns (uint256) { return _FUNDS_USED_[user]; } // ============ Funding Functions ============ function depositToken(uint256 amount) external preventReentrant onlyOwner { require(block.timestamp < _START_TIME_, "FUNDING_ALREADY_STARTED"); IERC20(_TOKEN_ADDRESS_).safeTransferFrom(msg.sender, address(this), amount); _TOTAL_TOKEN_AMOUNT_ = _TOTAL_TOKEN_AMOUNT_.add(amount); } function depositFunds(address to) external preventReentrant returns (uint256 newTokenAllocation) { // input fund check uint256 inputFund = IERC20(_FUNDS_ADDRESS_).balanceOf(address(this)).sub(_FUNDS_RESERVE_); _FUNDS_RESERVE_ = _FUNDS_RESERVE_.add(inputFund); if (_QUOTA_ != address(0)) { require( inputFund.add(_FUNDS_USED_[to]) <= uint256(IQuota(_QUOTA_).getUserQuota(to)), "QUOTA_EXCEED" ); } // allocation calculation uint256 currentPrice = getCurrentPrice(); newTokenAllocation = DecimalMath.divFloor(inputFund, currentPrice); if (newTokenAllocation.add(_TOTAL_ALLOCATED_TOKEN_) > _TOTAL_TOKEN_AMOUNT_) { newTokenAllocation = _TOTAL_TOKEN_AMOUNT_.sub(_TOTAL_ALLOCATED_TOKEN_); uint256 fundUsed = DecimalMath.mulFloor(newTokenAllocation, currentPrice); _FUNDS_USED_[to] = _FUNDS_USED_[to].add(fundUsed); _TOTAL_RAISED_FUNDS_ = _TOTAL_RAISED_FUNDS_.add(fundUsed); _FUNDS_UNUSED_[to] = _FUNDS_UNUSED_[to].add(inputFund.sub(fundUsed)); } else { _FUNDS_USED_[to] = _FUNDS_USED_[to].add(inputFund); _TOTAL_RAISED_FUNDS_ = _TOTAL_RAISED_FUNDS_.add(inputFund); } _TOKEN_ALLOCATION_[to] = _TOKEN_ALLOCATION_[to].add(newTokenAllocation); _TOTAL_ALLOCATED_TOKEN_ = _TOTAL_ALLOCATED_TOKEN_.add(newTokenAllocation); } function withdrawFunds(address to, uint256 amount) external preventReentrant { require(_FUNDS_UNUSED_[msg.sender] >= amount, "UNUSED_FUND_NOT_ENOUGH"); _FUNDS_UNUSED_[msg.sender] = _FUNDS_UNUSED_[msg.sender].sub(amount); IERC20(_FUNDS_ADDRESS_).safeTransfer(to, amount); _FUNDS_RESERVE_ = _FUNDS_RESERVE_.sub(amount); } function withdrawUnallocatedToken(address to) external preventReentrant onlyOwner { IERC20(_TOKEN_ADDRESS_).safeTransfer(to, _TOTAL_TOKEN_AMOUNT_.sub(_TOTAL_ALLOCATED_TOKEN_)); _TOTAL_TOKEN_AMOUNT_ = _TOTAL_ALLOCATED_TOKEN_; } }