/**
* Source Code first verified at https://etherscan.io on Sunday, April 21, 2019
(UTC) */
pragma solidity 0.5.4;
/**
* @title Ownable
* @dev The Ownable contract has an owner address, and provides basic authorization control
* functions, this simplifies the implementation of "user permissions".
*/
contract Ownable {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev The Ownable constructor sets the original `owner` of the contract to the sender
* account.
*/
constructor () internal {
_owner = msg.sender;
emit OwnershipTransferred(address(0), _owner);
}
/**
* @return the address of the owner.
*/
function owner() public view returns (address) {
return _owner;
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
require(isOwner());
_;
}
/**
* @return true if `msg.sender` is the owner of the contract.
*/
function isOwner() public view returns (bool) {
return msg.sender == _owner;
}
/**
* @dev Allows the current owner to relinquish control of the contract.
* @notice Renouncing to ownership will leave the contract without an owner.
* It will not be possible to call the functions with the `onlyOwner`
* modifier anymore.
*/
function renounceOwnership() public onlyOwner {
emit OwnershipTransferred(_owner, address(0));
_owner = address(0);
}
/**
* @dev Allows the current owner to transfer control of the contract to a newOwner.
* @param newOwner The address to transfer ownership to.
*/
function transferOwnership(address newOwner) public onlyOwner {
_transferOwnership(newOwner);
}
/**
* @dev Transfers control of the contract to a newOwner.
* @param newOwner The address to transfer ownership to.
*/
function _transferOwnership(address newOwner) internal {
require(newOwner != address(0));
emit OwnershipTransferred(_owner, newOwner);
_owner = newOwner;
}
}
/**
* @title Helps contracts guard against reentrancy attacks.
* @author Remco Bloemen <[email protected]π.com>, Eenae <[email protected]>
* @dev If you mark a function `nonReentrant`, you should also
* mark it `external`.
*/
contract ReentrancyGuard {
/// @dev counter to allow mutex lock with only one SSTORE operation
uint256 private _guardCounter;
constructor () internal {
// The counter starts at one to prevent changing it from zero to a non-zero
// value, which is a more expensive operation.
_guardCounter = 1;
}
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and make it call a
* `private` function that does the actual work.
*/
modifier nonReentrant() {
_guardCounter += 1;
uint256 localCounter = _guardCounter;
_;
require(localCounter == _guardCounter);
}
}
/**
* @title SafeMath
* @dev Unsigned math operations with safety checks that revert on error
*/
library SafeMath {
/**
* @dev Multiplies two unsigned integers, reverts on overflow.
*/
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522
if (a == 0) {
return 0;
}
uint256 c = a * b;
require(c / a == b);
return c;
}
/**
* @dev Integer division of two unsigned integers truncating the quotient, reverts on division by zero.
*/
function div(uint256 a, uint256 b) internal pure returns (uint256) {
// Solidity only automatically asserts when dividing by 0
require(b > 0);
uint256 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
return c;
}
/**
* @dev Subtracts two unsigned integers, reverts on overflow (i.e. if subtrahend is greater than minuend).
*/
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
require(b <= a);
uint256 c = a - b;
return c;
}
/**
* @dev Adds two unsigned integers, reverts on overflow.
*/
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a);
return c;
}
/**
* @dev Divides two unsigned integers and returns the remainder (unsigned integer modulo),
* reverts when dividing by zero.
*/
function mod(uint256 a, uint256 b) internal pure returns (uint256) {
require(b != 0);
return a % b;
}
}
// ==========================================================
// Copyright 2018 Confideal Ltd. All rights reserved.
// ==========================================================
contract OTCDeal is ReentrancyGuard {
using SafeMath for uint256;
uint8 constant public version = 1;
enum Status {
Running,
CloseoutProposed,
ClosedOut,
Terminated,
Arbitration,
Resolved
}
Status public status = Status.Running;
uint32 public statusTime = uint32(now);
uint32 public paymentDeadline; // timestamp
bytes32[] public dataHashes;
address payable public seller;
address payable public buyer;
address public sellerPartner;
address public buyerPartner;
uint256 public price;
uint256 public deskFee;
uint256 public closeoutCredit;
bool public isRefundBySellerSet;
bool public isRefundByBuyerSet;
bool public sellerAssetSent;
bool public buyerAssetSent;
uint256 public refundBySeller;
uint256 public refundByBuyer;
uint256 public sellerAsset;
uint256 public buyerAsset;
bytes32 public claimHash;
OTCDesk private desk;
event PaymentDeadlineProlongation();
event CloseoutProposition();
event Closeout();
event Termination();
event Arbitration();
event DisputeResolution();
event SellerAssetWithdrawal();
event BuyerAssetWithdrawal();
constructor(
bytes32 _dataHash,
address payable _seller,
address payable _buyer,
address _sellerPartner,
address _buyerPartner,
uint256 _price,
uint32 _paymentWindow,
bool _buyerIsTaker
)
public
payable
{
deskFee = _price.div(100);
if (_buyerIsTaker) {
require(msg.value == _price.add(deskFee));
} else {
require(msg.value == _price);
}
desk = OTCDesk(msg.sender);
dataHashes.push(_dataHash);
seller = _seller;
buyer = _buyer;
sellerPartner = _sellerPartner;
buyerPartner = _buyerPartner;
price = _price;
paymentDeadline = uint32(now.add(_paymentWindow));
}
function transferCloseoutCredit()
external
payable
nonReentrant
{
require(msg.sender == address(desk));
closeoutCredit = closeoutCredit.add(msg.value);
require(buyer.send(closeoutCredit));
}
function prolong(uint32 _paymentWindow, bytes32 _dataHash)
external
{
require(status == Status.Running);
require(msg.sender == seller);
uint32 newDeadline = uint32(now.add(_paymentWindow));
require(newDeadline >= paymentDeadline);
dataHashes.push(_dataHash);
paymentDeadline = newDeadline;
emit PaymentDeadlineProlongation();
}
function terminate()
external
nonReentrant
{
if (msg.sender == buyer) {
require(status == Status.Running || status == Status.CloseoutProposed);
} else {
require(msg.sender == seller);
require(status == Status.Running);
require(paymentDeadline < now);
}
emit Termination();
status = Status.Terminated;
statusTime = uint32(now);
sellerAsset = address(this).balance;
sellerAssetSent = seller.send(sellerAsset);
}
function closeOut(uint256 _refund)
external
nonReentrant
{
require(status == Status.Running || status == Status.CloseoutProposed);
require(_refund <= address(this).balance.sub(deskFee));
if (msg.sender == seller) {
if (_refund > 0) {
require(!isRefundBySellerSet || _refund != refundBySeller);
isRefundBySellerSet = true;
refundBySeller = _refund;
}
} else {
require(msg.sender == buyer);
require(!isRefundByBuyerSet || _refund != refundByBuyer);
isRefundByBuyerSet = true;
refundByBuyer = _refund;
}
if (msg.sender == buyer || _refund > 0) {
emit CloseoutProposition();
if (status == Status.Running) {
status = Status.CloseoutProposed;
statusTime = uint32(now);
}
}
if ((isRefundBySellerSet && isRefundByBuyerSet && refundBySeller == refundByBuyer)
|| (_refund == 0 && msg.sender == seller)) {
emit Closeout();
status = Status.ClosedOut;
statusTime = uint32(now);
transferAssets(_refund);
}
}
function escalate(bytes32 _claimHash)
external
{
require(msg.sender == seller || msg.sender == buyer);
require(status == Status.Running || status == Status.CloseoutProposed);
// paymentDeadline + 2 hours
require(now >= uint256(paymentDeadline).add(7200));
claimHash = _claimHash;
emit Arbitration();
status = Status.Arbitration;
statusTime = uint32(now);
desk.assignArbitratorFromPool();
}
function resolveDispute(
bytes32 _dataHash,
uint256 _sellerAsset
)
external
nonReentrant
{
require(status == Status.Arbitration);
require(msg.sender == address(desk));
require(_sellerAsset <= address(this).balance.sub(deskFee));
emit DisputeResolution();
status = Status.Resolved;
statusTime = uint32(now);
dataHashes.push(_dataHash);
transferAssets(_sellerAsset);
}
function withdrawSellerAsset()
external
nonReentrant
{
require(status == Status.ClosedOut || status == Status.Terminated || status == Status.Resolved);
require(msg.sender == seller && sellerAsset > 0 && !sellerAssetSent);
sellerAssetSent = unsafeTransfer(seller, sellerAsset);
if (sellerAssetSent) {
emit SellerAssetWithdrawal();
}
}
function withdrawBuyerAsset()
external
nonReentrant
{
require(status == Status.ClosedOut || status == Status.Resolved);
require(msg.sender == buyer && buyerAsset > 0 && !buyerAssetSent);
buyerAssetSent = unsafeTransfer(buyer, buyerAsset);
if (buyerAssetSent) {
emit BuyerAssetWithdrawal();
}
}
function()
external
{
revert();
}
function transferAssets(uint256 _sellerAsset)
private
{
sellerAsset = _sellerAsset;
buyerAsset = address(this).balance.sub(deskFee).sub(sellerAsset);
uint256 closeoutCreditReturn;
if (closeoutCredit > 0) {
if (buyerAsset <= closeoutCredit) {
closeoutCreditReturn = buyerAsset;
} else {
closeoutCreditReturn = closeoutCredit;
}
buyerAsset = buyerAsset.sub(closeoutCreditReturn);
}
desk.collectFee.value(deskFee.add(closeoutCreditReturn))(closeoutCreditReturn);
if (sellerAsset > 0) {
sellerAssetSent = seller.send(sellerAsset);
}
if (buyerAsset > 0) {
buyerAssetSent = buyer.send(buyerAsset);
}
}
function unsafeTransfer(address _recipient, uint256 _amount)
private
returns (bool success)
{
(success,) = _recipient.call.value(_amount)("");
return success;
}
}
contract OTCDesk is Ownable, ReentrancyGuard {
using SafeMath for uint256;
uint8 constant public version = 1;
address public beneficiary = msg.sender;
address public arbitrationManager = msg.sender;
uint256 public confidealFund;
uint256 public closeoutCredit = 0.0017 ether;
address[] public arbitratorsPool;
mapping(address => address) public arbitrators; // deal => arbitrator
event DealCreation(address deal);
event FeePayment(address deal, uint256 amount);
event CloseoutCreditIssuance(address deal, uint256 amount);
event CloseoutCreditCollection(address deal, uint256 amount);
event ArbitratorAssignment(address deal, address arbitrator);
function newDeal(
bytes32 _dataHash,
address payable _buyer,
address _sellerPartner,
address _buyerPartner,
uint256 _price,
uint32 _paymentWindow,
bool _buyerIsTaker
)
public
payable
{
OTCDeal _deal = (new OTCDeal).value(msg.value)(
_dataHash,
msg.sender,
_buyer,
_sellerPartner,
_buyerPartner,
_price,
_paymentWindow,
_buyerIsTaker
);
emit DealCreation(address(_deal));
if (_buyer.balance < closeoutCredit) {
uint256 _closeoutCredit = closeoutCredit.sub(_buyer.balance);
if (confidealFund >= _closeoutCredit) {
confidealFund = confidealFund.sub(_closeoutCredit);
_deal.transferCloseoutCredit.value(_closeoutCredit)();
emit CloseoutCreditIssuance(address(_deal), _closeoutCredit);
}
}
}
function setBeneficiary(address _beneficiary)
external
onlyOwner
{
beneficiary = _beneficiary;
}
function setArbitrationManager(address _arbitrationManager)
external
onlyOwner
{
arbitrationManager = _arbitrationManager;
}
function setCloseoutCredit(uint256 _closeoutCredit)
external
onlyOwner
{
closeoutCredit = _closeoutCredit;
}
function collectFee(uint256 _closeoutCreditReturn)
external
payable
{
uint256 fee = msg.value.sub(_closeoutCreditReturn);
confidealFund = confidealFund.add(fee);
emit FeePayment(msg.sender, fee);
if (_closeoutCreditReturn > 0) {
confidealFund = confidealFund.add(_closeoutCreditReturn);
emit CloseoutCreditCollection(msg.sender, _closeoutCreditReturn);
}
}
function arbitratorsPoolSize()
external
view
returns (uint)
{
return arbitratorsPool.length;
}
function addArbitratorToPool(address _arbitrator)
external
{
require(msg.sender == arbitrationManager);
arbitratorsPool.push(_arbitrator);
}
function removeArbitratorFromPool(uint _index)
external
{
require(msg.sender == arbitrationManager);
require(arbitratorsPool.length > 0);
arbitratorsPool[_index] = arbitratorsPool[arbitratorsPool.length - 1];
arbitratorsPool.pop();
}
function assignArbitratorFromPool()
external
{
if (arbitratorsPool.length == 0) {
return;
}
address _arbitrator = arbitratorsPool[block.number % arbitratorsPool.length];
arbitrators[msg.sender] = _arbitrator;
emit ArbitratorAssignment(msg.sender, _arbitrator);
}
function assignArbitrator(address _deal, address _arbitrator)
external
{
require(msg.sender == arbitrationManager);
arbitrators[_deal] = _arbitrator;
emit ArbitratorAssignment(_deal, _arbitrator);
}
function resolveDispute(address _deal, bytes32 _dataHash, uint256 _sellerAsset)
external
{
require(msg.sender == arbitrators[_deal]);
OTCDeal(_deal).resolveDispute(_dataHash, _sellerAsset);
}
function withdraw(uint256 _rest)
external
{
require(msg.sender == beneficiary);
uint256 _amount = confidealFund.sub(_rest);
require(_amount > 0);
confidealFund = confidealFund.sub(_amount);
(bool _successfulTransfer,) = beneficiary.call.value(_amount)("");
require(_successfulTransfer);
}
function contribute()
external
payable
{
confidealFund = confidealFund.add(msg.value);
}
function()
external
{
revert();
}
}