Skip to content
This repository was archived by the owner on Sep 20, 2019. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
213 changes: 160 additions & 53 deletions contracts/Exchange.sol
Original file line number Diff line number Diff line change
@@ -1,27 +1,41 @@
pragma solidity ^0.4.21;
pragma experimental ABIEncoderV2;

import "./Libraries/SafeMath.sol";
import "./ExchangeInterface.sol";
import "./HookSubscriber.sol";
import "./Libraries/OrderLibrary.sol";
import "./Libraries/ExchangeLibrary.sol";
import "./Ownership/Ownable.sol";
import "./Tokens/ERC20.sol";

contract Exchange is Ownable, ExchangeInterface {
import "@DexyProject/signature-validator/contracts/SignatureValidator.sol";

contract Exchange is ExchangeInterface, Ownable {

using SafeMath for *;
using OrderLibrary for OrderLibrary.Order;
using ExchangeLibrary for ExchangeLibrary.Exchange;

VaultInterface public vault;

uint public takerFee;
address public feeAccount;

mapping (address => mapping (bytes32 => bool)) public orders;
mapping (bytes32 => uint) public fills;
mapping (bytes32 => bool) public cancelled;
mapping (address => bool) public subscribed;

address constant public ETH = 0x0;

uint256 constant public MAX_FEE = 5000000000000000; // 0.5% ((0.5 / 100) * 10**18)

ExchangeLibrary.Exchange public exchange;
uint256 constant public MAX_ROUNDING_PERCENTAGE = 1000; // 0.1%
uint256 constant public MAX_HOOK_GAS = 40000; // enough for a storage write and some accounting logic

function Exchange(uint _takerFee, address _feeAccount, VaultInterface _vault) public {
require(address(_vault) != 0x0);
setFees(_takerFee);
setFeeAccount(_feeAccount);
exchange.vault = _vault;
vault = _vault;
}

/// @dev Withdraws tokens accidentally sent to this contract.
Expand All @@ -38,63 +52,95 @@ contract Exchange is Ownable, ExchangeInterface {

/// @dev Subscribes user to trade hooks.
function subscribe() external {
require(!exchange.subscribed[msg.sender]);
exchange.subscribed[msg.sender] = true;
require(!subscribed[msg.sender]);
subscribed[msg.sender] = true;
emit Subscribed(msg.sender);
}

/// @dev Unsubscribes user from trade hooks.
function unsubscribe() external {
require(exchange.subscribed[msg.sender]);
exchange.subscribed[msg.sender] = false;
require(subscribed[msg.sender]);
subscribed[msg.sender] = false;
emit Unsubscribed(msg.sender);
}

/// @dev Takes an order.
/// @param addresses Array of trade's maker, makerToken and takerToken.
/// @param values Array of trade's makerTokenAmount, takerTokenAmount, expires and nonce.
/// @param order Order to take.
/// @param signature Signed order along with signature mode.
/// @param maxFillAmount Maximum amount of the order to be filled.
function trade(address[3] addresses, uint[4] values, bytes signature, uint maxFillAmount) external {
exchange.trade(OrderLibrary.createOrder(addresses, values), msg.sender, signature, maxFillAmount);
function trade(OrderLibrary.Order order, bytes signature, uint maxFillAmount) external {
require(msg.sender != order.maker);
bytes32 hash = order.hash();

require(order.makerToken != order.takerToken);
require(canTrade(order, signature, hash));

uint fillAmount = SafeMath.min256(maxFillAmount, availableAmount(order, hash));

require(roundingPercent(fillAmount, order.takerTokenAmount, order.makerTokenAmount) <= MAX_ROUNDING_PERCENTAGE);
require(vault.balanceOf(order.takerToken, msg.sender) >= fillAmount);

uint makeAmount = order.makerTokenAmount.mul(fillAmount).div(order.takerTokenAmount);
uint tradeTakerFee = makeAmount.mul(takerFee).div(1 ether);

if (tradeTakerFee > 0) {
vault.transfer(order.makerToken, order.maker, feeAccount, tradeTakerFee);
}

vault.transfer(order.takerToken, msg.sender, order.maker, fillAmount);
vault.transfer(order.makerToken, order.maker, msg.sender, makeAmount.sub(tradeTakerFee));

fills[hash] = fills[hash].add(fillAmount);
assert(fills[hash] <= order.takerTokenAmount);

if (subscribed[order.maker]) {
order.maker.call.gas(MAX_HOOK_GAS)(
HookSubscriber(order.maker).tradeExecuted.selector,
order.takerToken,
fillAmount
);
}

emit Traded(
hash,
order.makerToken,
makeAmount,
order.takerToken,
fillAmount,
order.maker,
msg.sender
);
}

/// @dev Cancels an order.
/// @param addresses Array of trade's maker, makerToken and takerToken.
/// @param values Array of trade's makerTokenAmount, takerTokenAmount, expires and nonce.
function cancel(address[3] addresses, uint[4] values) external {
OrderLibrary.Order memory order = OrderLibrary.createOrder(addresses, values);

/// @param order Order struct for the cancelling order.
function cancel(OrderLibrary.Order order) external {
require(msg.sender == order.maker);
require(order.makerTokenAmount > 0 && order.takerTokenAmount > 0);

bytes32 hash = order.hash();
require(exchange.fills[hash] < order.takerTokenAmount);
require(!exchange.cancelled[hash]);
require(fills[hash] < order.takerTokenAmount);
require(!cancelled[hash]);

exchange.cancelled[hash] = true;
cancelled[hash] = true;
emit Cancelled(hash);
}

/// @dev Creates an order which is then indexed in the orderbook.
/// @param addresses Array of trade's makerToken and takerToken.
/// @param values Array of trade's makerTokenAmount, takerTokenAmount, expires and nonce.
function order(address[2] addresses, uint[4] values) external {
OrderLibrary.Order memory order = OrderLibrary.createOrder(
[msg.sender, addresses[0], addresses[1]],
values
);
/// @param order Order to create.
function order(OrderLibrary.Order order) external {
order.maker = msg.sender;

require(exchange.vault.isApproved(order.maker, this));
require(exchange.vault.balanceOf(order.makerToken, order.maker) >= order.makerTokenAmount);
require(vault.isApproved(order.maker, this));
require(vault.balanceOf(order.makerToken, order.maker) >= order.makerTokenAmount);
require(order.makerToken != order.takerToken);
require(order.makerTokenAmount > 0);
require(order.takerTokenAmount > 0);

bytes32 hash = order.hash();

require(!exchange.orders[msg.sender][hash]);
exchange.orders[msg.sender][hash] = true;
require(!orders[msg.sender][hash]);
orders[msg.sender][hash] = true;

emit Ordered(
order.maker,
Expand All @@ -108,65 +154,126 @@ contract Exchange is Ownable, ExchangeInterface {
}

/// @dev Checks if a order can be traded.
/// @param addresses Array of trade's maker, makerToken and takerToken.
/// @param values Array of trade's makerTokenAmount, takerTokenAmount, expires and nonce.
/// @param order Order to check.
/// @param signature Signed order along with signature mode.
/// @return Boolean if order can be traded
function canTrade(address[3] addresses, uint[4] values, bytes signature)
external
view
returns (bool)
{
OrderLibrary.Order memory order = OrderLibrary.createOrder(addresses, values);
return exchange.canTrade(order, signature, order.hash());
function canTrade(OrderLibrary.Order order, bytes signature) external view returns (bool) {
bytes32 hash = order.hash();
return canTrade(order, signature, hash);
}

/// @dev Returns if user has subscribed to trade hooks.
/// @param subscriber Address of the subscriber.
/// @return Boolean if user is subscribed.
function isSubscribed(address subscriber) external view returns (bool) {
return exchange.subscribed[subscriber];
return subscribed[subscriber];
}

/// @dev Checks how much of an order can be filled.
/// @param addresses Array of trade's maker, makerToken and takerToken.
/// @param values Array of trade's makerTokenAmount, takerTokenAmount, expires and nonce.
/// @param order Order to check.
/// @return Amount of the order which can be filled.
function availableAmount(address[3] addresses, uint[4] values) external view returns (uint) {
OrderLibrary.Order memory order = OrderLibrary.createOrder(addresses, values);
return exchange.availableAmount(order, order.hash());
function availableAmount(OrderLibrary.Order order) external view returns (uint) {
return availableAmount(order, order.hash());
}

/// @dev Returns how much of an order was filled.
/// @param hash Hash of the order.
/// @return Amount which was filled.
function filled(bytes32 hash) external view returns (uint) {
return exchange.fills[hash];
return fills[hash];
}

/// @dev Sets the taker fee.
/// @param _takerFee New taker fee.
function setFees(uint _takerFee) public onlyOwner {
require(_takerFee <= MAX_FEE);
exchange.takerFee = _takerFee;
takerFee = _takerFee;
}

/// @dev Sets the account where fees will be transferred to.
/// @param _feeAccount Address for the account.
function setFeeAccount(address _feeAccount) public onlyOwner {
require(_feeAccount != 0x0);
exchange.feeAccount = _feeAccount;
feeAccount = _feeAccount;
}

function vault() public view returns (VaultInterface) {
return exchange.vault;
return vault;
}

/// @dev Checks if an order was created on chain.
/// @param user User who created the order.
/// @param hash Hash of the order.
/// @return Boolean if the order was created on chain.
function isOrdered(address user, bytes32 hash) public view returns (bool) {
return exchange.orders[user][hash];
return orders[user][hash];
}

/// @dev Indicates whether or not an certain amount of an order can be traded.
/// @param order Order to be traded.
/// @param signature Signed order along with signature mode.
/// @param hash Hash of the order.
/// @return Boolean if order can be traded
function canTrade(OrderLibrary.Order memory order, bytes signature, bytes32 hash)
internal
view
returns (bool)
{
// if the order has never been traded against, we need to check the sig.
if (fills[hash] == 0) {
// ensures order was either created on chain, or signature is valid
if (!isOrdered(order.maker, hash) && !SignatureValidator.isValidSignature(hash, order.maker, signature)) {
return false;
}
}

if (cancelled[hash]) {
return false;
}

if (!vault.isApproved(order.maker, this)) {
return false;
}

if (order.takerTokenAmount == 0) {
return false;
}

if (order.makerTokenAmount == 0) {
return false;
}

// ensures that the order still has an available amount to be filled.
if (availableAmount(order, hash) == 0) {
return false;
}

return order.expires > now;
}

/// @dev Returns the maximum available amount that can be taken of an order.
/// @param order Order to check.
/// @param hash Hash of the order.
/// @return Amount of the order that can be filled.
function availableAmount(OrderLibrary.Order memory order, bytes32 hash) internal view returns (uint) {
return SafeMath.min256(
order.takerTokenAmount.sub(fills[hash]),
vault.balanceOf(order.makerToken, order.maker).mul(order.takerTokenAmount).div(order.makerTokenAmount)
);
}

/// @dev Returns the percentage which was rounded when dividing.
/// @param numerator Numerator.
/// @param denominator Denominator.
/// @param target Value to multiply with.
/// @return Percentage rounded.
function roundingPercent(uint numerator, uint denominator, uint target) internal pure returns (uint) {
// Inspired by https://github.com/0xProject/contracts/blob/1.0.0/contracts/sol#L472-L490
uint remainder = mulmod(target, numerator, denominator);
if (remainder == 0) {
return 0;
}

return remainder.mul(1000000).div(numerator.mul(target));
}
}
15 changes: 8 additions & 7 deletions contracts/ExchangeInterface.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ pragma solidity ^0.4.21;

import "./Vault/VaultInterface.sol";

// @todo update once we can have structs
interface ExchangeInterface {

event Subscribed(address indexed user);
Expand Down Expand Up @@ -32,14 +33,14 @@ interface ExchangeInterface {
function subscribe() external;
function unsubscribe() external;

function trade(address[3] addresses, uint[4] values, bytes signature, uint maxFillAmount) external;
function cancel(address[3] addresses, uint[4] values) external;
function order(address[2] addresses, uint[4] values) external;
// function trade(address[3] addresses, uint[4] values, bytes signature, uint maxFillAmount) external;
// function cancel(address[3] addresses, uint[4] values) external;
// function order(address[2] addresses, uint[4] values) external;

function canTrade(address[3] addresses, uint[4] values, bytes signature)
external
view
returns (bool);
// function canTrade(address[3] addresses, uint[4] values, bytes signature)
// external
// view
// returns (bool);

function isSubscribed(address subscriber) external view returns (bool);
function availableAmount(address[3] addresses, uint[4] values) external view returns (uint);
Expand Down
7 changes: 4 additions & 3 deletions test/mocks/HookSubscriberMock.sol
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
pragma solidity ^0.4.18;

import "./../../contracts/ExchangeInterface.sol";
import "./../../contracts/Exchange.sol";
import "./../../contracts/Libraries/OrderLibrary.sol";

contract HookSubscriberMock {

Expand All @@ -10,10 +11,10 @@ contract HookSubscriberMock {
tokens[token] += amount;
}

function createOrder(address[2] addresses, uint[4] values, ExchangeInterface exchange) external {
function createOrder(address[2] addresses, uint[4] values, Exchange exchange) external {
exchange.subscribe();
exchange.vault().approve(exchange);
exchange.vault().deposit(addresses[0], values[0]);
exchange.order(addresses, values);
exchange.order(OrderLibrary.createOrder([msg.sender, addresses[0], addresses[1]], values));
}
}