// File: Auction.sol



// SPDX-License-Identifier: MIT
pragma solidity >=0.4.22 <0.9.0;

import "./SimpleSale.sol";

contract Auction is SimpleSale {

    using SafeMath for uint256;
    
    uint256 public initialPrice;
    uint256 public maximumPrice;
    uint256 public bidStep;
    uint256 public period;
    bool public bidCompleted;

    constructor(
        address _administrator,
        address  _seller,
        uint256 _initialPrice,
        uint256 _maximumPrice,
        uint256 _bidStep,
        uint256 _period,
        string  memory _title,
        string  memory _description,
        address _feeTo,
        uint256 _feeRate,
        uint256 _collateralFactor
    ) SimpleSale(_administrator, _seller, 0, _title, _description, _feeTo, _feeRate, _collateralFactor) {
        price = initialPrice - bidStep;
        initialPrice = _initialPrice;
        maximumPrice = _maximumPrice;
        bidStep = _bidStep;
        period = _period;
        bidCompleted = false;
    }

    function bid(uint256 amount) external {
        require( !bidCompleted );
        require( sellerCollateralAmount >= maximumPrice.mul(collateralFactor).div(1000));

        if( msg.sender == buyer ){
            buyerCollateralAmount += amount;
        }
        else{
            require( amount >= price.add(bidStep) && amount <= maximumPrice);
            TransferHelper.safeTransfer( USDToken, buyer, buyerCollateralAmount);
            buyer = msg.sender;
            buyerCollateralAmount = amount;
        }
        price = buyerCollateralAmount;
        TransferHelper.safeTransferFrom(USDToken, msg.sender, address(this), amount);
    }

    function depositCollateral(uint amount) external virtual override onlySeller {
        require( status == Status.Created );
        require( amount > 0 );
        sellerCollateralAmount = sellerCollateralAmount.add(amount);
        TransferHelper.safeTransferFrom(USDToken, msg.sender, address(this), amount);
        require(IUSDT(USDToken).balanceOf(address(this)) >= sellerCollateralAmount.add(buyerCollateralAmount));
        emit Deposited( msg.sender, amount);
    }

    function withdraw(uint256 amount) external onlySeller override {
        require( status == Status.Created );
        require( bidCompleted && buyerCollateralAmount == 0 );
        sellerCollateralAmount = sellerCollateralAmount.sub(amount);
        TransferHelper.safeTransfer( USDToken, msg.sender, amount);
        require(IUSDT(USDToken).balanceOf(address(this)) >= sellerCollateralAmount.add(buyerCollateralAmount));
        emit Withdraw(msg.sender, amount);
    }

    function completeBid() external onlySeller {
        require( block.timestamp >= createdAt.add(period));
        bidCompleted = true;
        if( buyerCollateralAmount > 0){
            status = Status.ReadyToStart;
        }
    }
}
// File: DAssetTrade.sol



// SPDX-License-Identifier: MIT
pragma solidity >=0.4.22 <0.9.0;
import "./Auction.sol";

contract DAssetTrade {

  address   public administrator;
  
  uint256   public feeRate = 40;            // 4%
  uint256   public collateralFactor = 100;  // 10%
  address   public feeTo;

  address[] public escrows;
  address[] public simpleSales;
  address[] public auctions;

  event escrowCreated(address _newEscrowAddr);
  event simpleSaleCreated(address _newProductAddr);
  event auctionCreated(address _newProductAddr);

  modifier onlyAdmin() {
    require( msg.sender == administrator );
    _;
  }  

  constructor () {
    administrator = msg.sender;
    feeTo         = msg.sender;
  }


  function changeAdmin(address _newAdmin) external onlyAdmin {
    administrator = _newAdmin;
  }

  function setFeeTo(address _feeTo) external onlyAdmin {
    feeTo = _feeTo;
  }

  function setFeeRate(uint _feeRate) external onlyAdmin {
    feeRate = _feeRate;
  }

  function setCollateralFactor(uint _collateralFactor) external onlyAdmin {
    collateralFactor = _collateralFactor;
  }

  function createEscrow(
    address  _buyer,
    uint256 _price,
    string memory _title,
    string memory _description
  ) external {
    Escrow escrow = new Escrow(administrator, msg.sender, _buyer, _price, _title, _description, feeTo, feeRate, collateralFactor);
    escrows.push(address(escrow));
    emit escrowCreated(address(escrow));
  }

  function createSimpleSale(
    uint256 _price,
    string memory _title,
    string memory _description
  ) external {
    SimpleSale simple = new SimpleSale(administrator, msg.sender, _price, _title, _description, feeTo, feeRate, collateralFactor);
    simpleSales.push(address(simple));
    emit simpleSaleCreated(address(simple));
  }

  function createAuction(
    uint256 _initialPrice,
    uint256 _maximumPrice,
    uint256 _bidStep,
    uint256 _period,
    string memory _title,
    string memory _description
  ) external {
    Auction auction = new Auction(administrator, msg.sender, _initialPrice, _maximumPrice, _bidStep, _period, _title, _description, feeTo, feeRate, collateralFactor);
    auctions.push(address(auction));
    emit auctionCreated(address(auction));
  }

  function getDetail() external view returns (address, address, uint256, uint256, address[] memory, address[] memory, address[] memory) {
    return (administrator, feeTo, feeRate, collateralFactor, escrows, simpleSales, auctions);
  }
}

// File: Escrow.sol



// SPDX-License-Identifier: MIT
pragma solidity >=0.4.22 <0.9.0;
pragma experimental ABIEncoderV2;

import "./SafeMath.sol";
import "./TransferHelper.sol";
import "./IUSDT.sol";

contract Escrow {
  
  using SafeMath for uint256;

  struct Request {
    address     sender;
    RequestType reqType;
    uint256     reqValue;  // adjustment price
    uint256     reqAt;
    ReplyValue  replyValue;
    uint256     replyAt;
  }

  enum Status {
    Created,        
    ReadyToStart,        // seller and buyer both have deposited collateral funds.
    OnGoing,
    WaitingReply,
    WaitingAdminVerification,
    Finished       // contract has been completed.
  }

  enum RequestType {
    ReviewProduct,
    RejectProduct,
    AdjustPrice,
    AdminVerification
  }

  enum ReplyValue {
    Pending,
    Accept,
    Reject
  }

  address   public administrator;
  address   public seller;
  address   public buyer;
  uint256   public price;
  string    public title;
  string    public description;

  address   public feeTo;
  uint256   public feeRate;
  uint256   public collateralFactor; 
  uint256   public createdAt;
  Status    public status;

  uint256   public sellerCollateralAmount;
  uint256   public buyerCollateralAmount;

  Request public request;

  address   public USDToken = 0xdAC17F958D2ee523a2206206994597C13D831ec7;

  event Deposited(address depositer, uint256 amount);
  event Withdraw(address refunder, uint256 amount);
  event EscrowCompleted(uint256 amountToSeller, uint256 amountToBuyer);

  modifier onlyAdmin() {  
    require(msg.sender == administrator);
    _;
  }

  modifier onlySeller() {  
    require(msg.sender == seller);
    _;
  }

  modifier onlyBuyer() {
    require(msg.sender == buyer);
    _;
  }

  modifier onlySellerOrBuyer() {
    require(msg.sender == seller || msg.sender == buyer);
    _;
  }

  constructor(
    address _administrator,
    address _seller,
    address _buyer,
    uint256 _price,
    string  memory _title,
    string  memory _description,
    address _feeTo,
    uint256 _feeRate,
    uint256 _collateralFactor
  ) {

    administrator = _administrator;
    seller        = _seller;
    buyer         = _buyer;
    price         = _price;
    title         = _title;
    description   = _description;
    status        = Status.Created;
    createdAt     = block.timestamp;

    feeTo             = _feeTo;
    feeRate           = _feeRate;
    collateralFactor  = _collateralFactor;

    sellerCollateralAmount  = 0;
    buyerCollateralAmount   = 0;
  }

  function depositCollateral(uint256 amount) external virtual {
    require( msg.sender == seller || msg.sender == buyer);
    require( status == Status.Created );
    require( amount > 0 );

    if( msg.sender == seller ){
      sellerCollateralAmount = sellerCollateralAmount.add(amount);
    }
    else{
      buyerCollateralAmount = buyerCollateralAmount.add(amount);
    }

    TransferHelper.safeTransferFrom(USDToken, msg.sender, address(this), amount);

    if( buyerCollateralAmount >= price && sellerCollateralAmount >= price.mul(collateralFactor).div(1000) ){
      status = Status.ReadyToStart;
    }

    require(IUSDT(USDToken).balanceOf(address(this)) >= sellerCollateralAmount.add(buyerCollateralAmount));

    emit Deposited( msg.sender, amount);
  }

  function withdraw(uint256 amount) external onlySellerOrBuyer virtual {
    require ( status == Status.Created );
    require ( amount > 0 );

    if( msg.sender == seller ){
      sellerCollateralAmount = sellerCollateralAmount.sub(amount);
    }
    else{
      buyerCollateralAmount = buyerCollateralAmount.sub(amount);
    }
    
    TransferHelper.safeTransfer( USDToken, msg.sender, amount);

    require(IUSDT(USDToken).balanceOf(address(this)) >= sellerCollateralAmount.add(buyerCollateralAmount));

    emit Withdraw(msg.sender, amount);
  }

  function finalizeTransaction() external onlyBuyer {
    require ( status != Status.Finished );

    uint256 fee = price.mul(feeRate).div(1000);
    uint256 amountToSeller = sellerCollateralAmount.add(price).sub(fee);
    uint256 amountToBuyer = buyerCollateralAmount.sub(price);
    
    sellerCollateralAmount = 0;
    buyerCollateralAmount = 0;

    TransferHelper.safeTransfer(USDToken, feeTo, fee );
    TransferHelper.safeTransfer(USDToken, seller, amountToSeller);
    TransferHelper.safeTransfer(USDToken, buyer, amountToBuyer);

    status = Status.Finished;

    emit EscrowCompleted(amountToSeller, amountToBuyer);

  }

  function sendRequest(RequestType reqType, uint256 reqValue) external onlySellerOrBuyer {
 
    require( reqType >= RequestType.ReviewProduct && reqType <= RequestType.AdminVerification);

    if(reqType == RequestType.ReviewProduct){
      require( msg.sender == seller && status == Status.ReadyToStart);
    }else if(reqType == RequestType.AdminVerification){
      require( status == Status.ReadyToStart || status == Status.OnGoing || status == Status.WaitingReply);
    }else{
      require(msg.sender == buyer && status == Status.OnGoing);
    }

    if(reqType == RequestType.AdjustPrice){
      require(reqValue > 0 && reqValue < price);
    }else{
      require(reqValue == 0);
    }
    
    request = Request( msg.sender, reqType, reqValue, block.timestamp, ReplyValue.Pending, 0);
    status = Status.WaitingReply;

    if(reqType == RequestType.AdminVerification){
      status = Status.WaitingAdminVerification;
    }
  }

  function replyToRequest(ReplyValue _replyValue) external onlySellerOrBuyer {
    require(status == Status.WaitingReply);
    require( _replyValue == ReplyValue.Accept || _replyValue == ReplyValue.Reject);

    if(request.reqType == RequestType.ReviewProduct){
      require(msg.sender == buyer);
    }else{
      require(msg.sender == seller);
    }

    request.replyValue = _replyValue;
    request.replyAt = block.timestamp;

    if(request.reqType == RequestType.ReviewProduct){
      if( _replyValue == ReplyValue.Accept ){
        status = Status.OnGoing;
      }
      else{
        status = Status.ReadyToStart;
      }
    }else if(request.reqType == RequestType.RejectProduct){
      if( _replyValue == ReplyValue.Reject ){
        status = Status.OnGoing;
      }
      else{
        uint256 amountToSeller = sellerCollateralAmount;
        uint256 amountToBuyer = buyerCollateralAmount;

        sellerCollateralAmount = 0;
        buyerCollateralAmount = 0;

        TransferHelper.safeTransfer(USDToken, seller, amountToSeller);
        TransferHelper.safeTransfer(USDToken, buyer, amountToBuyer);

        status = Status.Finished;
        emit EscrowCompleted(amountToSeller, amountToBuyer);
      }
    }else if(request.reqType == RequestType.AdjustPrice){
      if(_replyValue == ReplyValue.Reject){
        status = Status.OnGoing;
      }
      else{
        price = request.reqValue;
        uint256 fee = price.mul(feeRate).div(1000);
        uint256 amountToSeller = sellerCollateralAmount.add(price).sub(fee);
        uint256 amountToBuyer = buyerCollateralAmount.sub(price);
        
        sellerCollateralAmount = 0;
        buyerCollateralAmount = 0;

        TransferHelper.safeTransfer(USDToken, feeTo, fee );
        TransferHelper.safeTransfer(USDToken, seller, amountToSeller);
        TransferHelper.safeTransfer(USDToken, buyer, amountToBuyer);

        status = Status.Finished;
        emit EscrowCompleted(amountToSeller, amountToBuyer);
      }
    }

  }

  function executeVerificationResult(uint256 amountToSeller, uint256 amountToBuyer ) external onlyAdmin {
    require(status == Status.WaitingAdminVerification);

    uint currentBalance = IUSDT(USDToken).balanceOf(address(this));
    require( currentBalance > amountToSeller.add(amountToBuyer) );
    request.replyValue = ReplyValue.Accept;
    request.replyAt = block.timestamp;

    uint256 fee = currentBalance.sub(amountToSeller).sub(amountToBuyer);

    sellerCollateralAmount = 0;
    buyerCollateralAmount = 0;

    TransferHelper.safeTransfer(USDToken, feeTo, fee );
    TransferHelper.safeTransfer(USDToken, seller, amountToSeller);
    TransferHelper.safeTransfer(USDToken, buyer, amountToBuyer);

    status = Status.Finished;
    emit EscrowCompleted(amountToSeller, amountToBuyer);
  }

  function setSellerCollateralFactor(uint256 _newCollateralFactor) external onlyBuyer {
    require(status == Status.Created);
    require(_newCollateralFactor < collateralFactor);
    collateralFactor = _newCollateralFactor;
  }

  function getDetail() external view returns(address, address, uint256, uint256, uint256, uint256, uint256, address, uint256) {
    return (
      seller,
      buyer,
      price,
      collateralFactor,
      createdAt,
      sellerCollateralAmount,
      buyerCollateralAmount,
      administrator,
      feeRate
    );
  }
}

// File: IUSDT.sol



// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.4.22 <0.9.0;

interface IUSDT {
    function approve(address _spender, uint256 _value) external;
    function balanceOf(address who) external view returns (uint256);
}
// File: Migrations.sol



// SPDX-License-Identifier: MIT
pragma solidity >=0.4.22 <0.9.0;

contract Migrations {
  address public owner = msg.sender;
  uint public last_completed_migration;

  modifier restricted() {
    require(
      msg.sender == owner,
      "This function is restricted to the contract's owner"
    );
    _;
  }

  function setCompleted(uint completed) public restricted {
    last_completed_migration = completed;
  }
}

// File: SafeMath.sol



// SPDX-License-Identifier: MIT
pragma solidity >=0.4.22 <0.9.0;

/**
 * @title SafeMath
 * @dev Math operations with safety checks that throw on error
 */
library SafeMath {
    // it is recommended to define functions which can neither read the state of blockchain nor write in it as pure instead of constant

    /**
    * @dev Multiplies two numbers, throws on overflow.
  */
    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
        if (a == 0) {
            return 0;
        }
        uint256 c = a * b;
        assert(c / a == b);
        return c;
    }

    /**
    * @dev Integer division of two numbers, truncating the quotient.
    */
    function div(uint256 a, uint256 b) internal pure returns (uint256) {
        // assert(b > 0); // Solidity automatically throws when dividing by 0
        // uint256 c = a / b;
        // assert(a == b * c + a % b); // There is no case in which this doesn't hold
        return a / b;
    }

    /**
    * @dev Subtracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend).
    */
    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
        assert(b <= a);
        return a - b;
    }

    /**
    * @dev Adds two numbers, throws on overflow.
    */
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        uint256 c = a + b;
        assert(c >= a);
        return c;
    }

}
// File: SimpleSale.sol



// SPDX-License-Identifier: MIT
pragma solidity >=0.4.22 <0.9.0;

import "./Escrow.sol";

contract SimpleSale is Escrow {

    using SafeMath for uint256;

    constructor(
        address _administrator,
        address  _seller,
        uint256 _price,
        string  memory _title,
        string  memory _description,
        address _feeTo,
        uint256 _feeRate,
        uint256 _collateralFactor
    ) Escrow(_administrator, _seller, address(0), _price, _title, _description, _feeTo, _feeRate, _collateralFactor) {

    }

    function depositCollateral(uint amount) external virtual override {
        require( buyer == address(0) || msg.sender == seller);
        require( status == Status.Created );
        require( amount > 0 );
        if( msg.sender == seller ){
            sellerCollateralAmount = sellerCollateralAmount.add(amount);
        }else{
            require( amount >= price );
            buyer = msg.sender;
            buyerCollateralAmount = amount;
        }
        TransferHelper.safeTransferFrom(USDToken, msg.sender, address(this), amount);

        if( buyerCollateralAmount >= price && sellerCollateralAmount >= price.mul(collateralFactor).div(1000) ){
            status = Status.ReadyToStart;
        }

        require(IUSDT(USDToken).balanceOf(address(this)) >= sellerCollateralAmount.add(buyerCollateralAmount));
        emit Deposited( msg.sender, amount);
    }
}
// File: TransferHelper.sol



// SPDX-License-Identifier: MIT
pragma solidity >=0.4.22 <0.9.0;

// helper methods for interacting with ERC20 tokens and sending ETH that do not consistently return true/false
library TransferHelper {
    function safeApprove(
        address token,
        address to,
        uint256 value
    ) internal {
        // bytes4(keccak256(bytes('approve(address,uint256)')));
        (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x095ea7b3, to, value));
        require(
            success && (data.length == 0 || abi.decode(data, (bool))),
            'TransferHelper::safeApprove: approve failed'
        );
    }

    function safeTransfer(
        address token,
        address to,
        uint256 value
    ) internal {
        // bytes4(keccak256(bytes('transfer(address,uint256)')));
        (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0xa9059cbb, to, value));
        require(
            success && (data.length == 0 || abi.decode(data, (bool))),
            'TransferHelper::safeTransfer: transfer failed'
        );
    }

    function safeTransferFrom(
        address token,
        address from,
        address to,
        uint256 value
    ) internal {
        // bytes4(keccak256(bytes('transferFrom(address,address,uint256)')));
        (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x23b872dd, from, to, value));
        require(
            success && (data.length == 0 || abi.decode(data, (bool))),
            'TransferHelper::transferFrom: transferFrom failed'
        );
    }

    function safeTransferETH(address to, uint256 value) internal {
        (bool success, ) = to.call{value: value}(new bytes(0));
        require(success, 'TransferHelper::safeTransferETH: ETH transfer failed');
    }
}