Skip to content

Instantly share code, notes, and snippets.

@jleeh
Created October 28, 2018 10:12
Show Gist options
  • Save jleeh/e94b110694c3e7e7dc81a06d69c123f7 to your computer and use it in GitHub Desktop.
Save jleeh/e94b110694c3e7e7dc81a06d69c123f7 to your computer and use it in GitHub Desktop.
pragma solidity ^0.4.23;
import "./interface/PoolOwnersInterface.sol";
import "./std/ERC20.sol";
import "./std/SafeMath.sol";
import "./std/Ownable.sol";
import "./lib/ItMap.sol";
/**
@title OwnersExchange
@dev Allow for trustless exchange of LP owners tokens
*/
contract OwnersExchange is Ownable {
using SafeMath for uint;
using itmap for itmap.itmap;
enum ORDER_TYPE {
NULL, BUY, SELL
}
uint public orderCount;
uint public fee;
uint public lockedFees;
mapping(uint => uint) public feeBalances;
address[] public addressRegistry;
mapping(address => uint) public addressIndex;
itmap.itmap orderBook;
PoolOwnersInterface public poolOwners;
ERC20 public feeToken;
event NewOrder(ORDER_TYPE indexed orderType, address indexed sender, uint price, uint amount);
event OrderRemoved(ORDER_TYPE indexed orderType, address indexed sender, uint price, uint amount);
event OrderFilled(ORDER_TYPE indexed orderType, address indexed sender, address receiver, uint price, uint amount);
/**
@dev Initialise the contract
@param _poolOwners Set the address of the PoolOwners contract used in this DEX
*/
constructor(address _poolOwners, address _feeToken) public {
require(_poolOwners != address(0), "_poolOwners needs to be set");
poolOwners = PoolOwnersInterface(_poolOwners);
feeToken = ERC20(_feeToken);
addressRegistry.push(address(0));
orderCount = 1;
}
/**
@dev Register an address to a uint allowing packing in orders
@param _address The address to register
*/
function addressRegister(address _address) private returns (uint) {
if (addressIndex[_address] != 0) {
return addressIndex[_address];
} else {
require(addressRegistry.length < 1 << 32, "Registered addresses hit maximum");
addressIndex[_address] = addressRegistry.length;
addressRegistry.push(_address);
return addressRegistry.length - 1;
}
}
/**
@dev ERC677 Reciever for fee token transfer (Always expected to be LINK)
@param _sender The address of the sender of the token
@param _value The amount of token received
@param _data Extra data, not needed in this use-case
*/
function onTokenTransfer(address _sender, uint256 _value, bytes _data) public {
require(msg.sender == address(feeToken), "Sender needs to be the fee token");
uint index = addressRegister(_sender);
feeBalances[index] = feeBalances[index].add(_value);
}
/**
@dev Allow users to withdraw any tokens used for fees
@param _value The amount wanting to be withdrawn
*/
function withdrawFeeToken(uint256 _value) public {
uint index = addressRegister(msg.sender);
require(feeBalances[index] >= _value, "You're withdrawing more than your balance");
feeBalances[index] = feeBalances[index].sub(_value);
feeToken.transfer(msg.sender, _value);
}
/**
@dev Set the fee percentage
@param _fee The percentage of fees to be taken in LINK
*/
function setFee(uint _fee) public onlyOwner {
require(_fee <= 500 finney, "Fees can't be more than 50%");
fee = _fee;
}
/**
@dev Returns the fee cost based on a price & amount
@param _price The price of the order
@param _amount The amount requested
*/
function feeForOrder(uint _price, uint _amount) public view returns (uint) {
return _price
.mul(_amount)
.div(1 ether)
.mul(fee)
.div(1 ether);
}
/**
@dev Returns the ETH cost of an order
@param _price The price of the order
@param _amount The amount requested
*/
function costOfOrder(uint _price, uint _amount) public pure returns (uint) {
return _price.mul(_amount).div(1 ether);
}
/**
@dev Create a new sell order
@param _price The price of the order per 1 ether of token
@param _amount The amount of tokens being sent
*/
function addSellOrder(uint _price, uint _amount) public {
require(is111bit(_price) && is111bit(_amount), "Price or amount exceeds 111 bits");
require(_price > 0, "Price needs to be greater than 0");
require(_amount > 0, "Amount needs to be greater than 0");
uint orderFee = feeForOrder(_price, _amount);
uint index = addressRegister(msg.sender);
if (orderFee > 0) {
require(feeBalances[index] >= orderFee, "You do not have enough deposited for fees");
feeBalances[index] = feeBalances[index].sub(orderFee);
feeBalances[0] = feeBalances[0].add(orderFee);
lockedFees = lockedFees.add(orderFee);
}
poolOwners.sendOwnershipFrom(msg.sender, this, _amount);
require(
!orderBook.insert(orderCount, (((uint(ORDER_TYPE.SELL) << 32 | index) << 111 | _price) << 111) | _amount),
"Map replacement detected"
);
orderCount += 1;
emit NewOrder(ORDER_TYPE.SELL, msg.sender, _price, _amount);
}
/**
@dev Add a new buy order, ETH sent needs to equal: (price * amount) / 18
@param _price The price of the buy order per 1 ether of LP token
@param _amount The amount of tokens wanting to be purchased
*/
function addBuyOrder(uint _price, uint _amount) public payable {
require(is111bit(_price) && is111bit(_amount), "Price or amount exceeds 111 bits");
require(_price > 0, "Price needs to be greater than 0");
require(_amount > 0, "Amount needs to be greater than 0");
uint orderFee = feeForOrder(_price, _amount);
uint index = addressRegister(msg.sender);
if (orderFee > 0) {
require(feeBalances[index] >= orderFee, "You do not have enough deposited for fees");
feeBalances[index] = feeBalances[index].sub(orderFee);
feeBalances[0] = feeBalances[0].add(orderFee);
lockedFees = lockedFees.add(orderFee);
}
uint cost = _price.mul(_amount).div(1 ether);
require(_price.mul(_amount) == cost.mul(1 ether), "The price and amount of this order is too small");
require(msg.value == cost, "ETH sent needs to equal the cost");
require(
!orderBook.insert(orderCount, (((uint(ORDER_TYPE.BUY) << 32 | index) << 111 | _price) << 111) | _amount),
"Map replacement detected"
);
orderCount += 1;
emit NewOrder(ORDER_TYPE.BUY, msg.sender, _price, _amount);
}
/**
@dev Remove a buy order and refund ETH back to the sender
@param _key The key of the order in the book
*/
function removeBuyOrder(uint _key) public {
uint order = orderBook.get(_key);
ORDER_TYPE orderType = ORDER_TYPE(order >> 254);
require(orderType == ORDER_TYPE.BUY, "This is not a buy order");
uint index = addressIndex[msg.sender];
require(index == (order << 2) >> 224, "You are not the sender of this order");
uint price = (order << 34) >> 145;
uint amount = (order << 145) >> 145;
require(orderBook.remove(_key), "Map remove failed");
uint orderFee = feeForOrder(price, amount);
feeBalances[index] = feeBalances[index].add(orderFee);
feeBalances[0] = feeBalances[0].sub(orderFee);
lockedFees = lockedFees.sub(orderFee);
uint cost = price.mul(amount).div(1 ether);
msg.sender.transfer(cost);
emit OrderRemoved(orderType, msg.sender, price, amount);
}
/**
@dev Remove a sell order and refund the LP tokens back to the sender
@param _key The key of the order in the book
*/
function removeSellOrder(uint _key) public {
uint order = orderBook.get(_key);
ORDER_TYPE orderType = ORDER_TYPE(order >> 254);
require(orderType == ORDER_TYPE.SELL, "This is not a sell order");
uint index = addressIndex[msg.sender];
require(index == (order << 2) >> 224, "You are not the sender of this order");
uint price = (order << 34) >> 145;
uint amount = (order << 145) >> 145;
require(orderBook.remove(_key), "Map remove failed");
uint orderFee = feeForOrder(price, amount);
feeBalances[index] = feeBalances[index].add(orderFee);
feeBalances[0] = feeBalances[0].sub(orderFee);
lockedFees = lockedFees.sub(orderFee);
poolOwners.sendOwnership(msg.sender, amount);
emit OrderRemoved(orderType, msg.sender, price, amount);
}
/**
@dev Fill a sell order in the order book
@dev Orders have to be filled in whole amounts
@param _key Key of the order as per orderbook
*/
function fillSellOrder(uint _key) public payable {
uint order = orderBook.get(_key);
ORDER_TYPE orderType = ORDER_TYPE(order >> 254);
require(orderType == ORDER_TYPE.SELL, "This is not a sell order");
uint index = addressRegister(msg.sender);
require(index != (order << 2) >> 224, "You cannot fill your own order");
uint price = (order << 34) >> 145;
uint amount = (order << 145) >> 145;
uint orderFee = feeForOrder(price, amount);
require(feeBalances[index] >= orderFee, "You do not have enough deposited fees to fill this order");
uint cost = price.mul(amount).div(1 ether);
require(msg.value == cost, "ETH sent needs to equal the cost");
require(orderBook.remove(_key), "Map remove failed");
addressRegistry[index].transfer(msg.value);
poolOwners.sendOwnership(msg.sender, amount);
if (orderFee > 0) {
feeBalances[index] = feeBalances[index].sub(orderFee);
feeBalances[0] = feeBalances[0].add(orderFee);
lockedFees = lockedFees.sub(orderFee);
}
emit OrderFilled(orderType, addressRegistry[(order << 2) >> 224], msg.sender, price, amount);
}
/**
@dev Fill a buy order in the order book
@dev Orders have to be filled in whole amounts
@param _key Key of the order, which is the buyers address
*/
function fillBuyOrder(uint _key) public {
uint order = orderBook.get(_key);
ORDER_TYPE orderType = ORDER_TYPE(order >> 254);
require(orderType == ORDER_TYPE.BUY, "This is not a buy order");
uint index = addressRegister(msg.sender);
require(index != (order << 2) >> 224, "You cannot fill your own order");
uint price = (order << 34) >> 145;
uint amount = (order << 145) >> 145;
uint orderFee = feeForOrder(price, amount);
require(feeBalances[index] >= orderFee, "You do not have enough deposited fees to fill this order");
uint cost = price.mul(amount).div(1 ether);
require(orderBook.remove(_key), "Map remove failed");
msg.sender.transfer(cost);
poolOwners.sendOwnershipFrom(msg.sender, addressRegistry[(order << 2) >> 224], amount);
if (orderFee > 0) {
feeBalances[index] = feeBalances[index].sub(orderFee);
feeBalances[0] = feeBalances[0].add(orderFee);
lockedFees = lockedFees.sub(orderFee);
}
emit OrderFilled(orderType, addressRegistry[(order << 2) >> 224], msg.sender, price, amount);
}
/**
@dev Send tokens earned via fees distribution back to PoolOwners to be re-distributed
*/
function withdrawFeesToPoolOwners() public {
uint feeBalance = feeBalances[0];
require(feeBalance > 0, "Contract doesn't have a fee balance");
feeBalances[0] = lockedFees;
feeToken.transfer(poolOwners, feeBalance.sub(lockedFees));
}
/**
@dev Get a single order by its key
@param _key The key of the order as per the book
*/
function getOrder(uint _key) public view returns (ORDER_TYPE, address, uint, uint) {
uint order = orderBook.get(_key);
return (
ORDER_TYPE(order >> 254),
addressRegistry[(order << 2) >> 224],
(order << 34) >> 145,
(order << 145) >> 145
);
}
/**
@dev Get a batch of 10 orders by a given array of keys
@dev ID's has to be equal or less than 10 in length, or an empty response is given
@param _start The starting index in the order book to return from
*/
function getOrders(uint _start) public view returns (
uint[10] keys,
address[10] addresses,
ORDER_TYPE[10] orderTypes,
uint[10] prices,
uint[10] amounts
) {
for (uint i = 0; i < 10; i++) {
if (orderBook.size() == _start + i) {
break;
}
uint key = orderBook.getKey(_start + i);
keys[i] = key;
uint order = orderBook.get(key);
addresses[i] = addressRegistry[(order << 2) >> 224];
orderTypes[i] = ORDER_TYPE(order >> 254);
prices[i] = (order << 34) >> 145;
amounts[i] = (order << 145) >> 145;
}
return (keys, addresses, orderTypes, prices, amounts);
}
/**
@dev Get an orderbook key from the orderbook index
@param _i The index to fetch the key for
*/
function getOrderBookKey(uint _i) public view returns (uint key) {
if (_i < orderBook.size()) {
key = orderBook.getKey(_i);
} else {
key = 0;
}
return key;
}
/**
@dev Get orderbook keys in batches of 10
@param _start The start of the index for the batch
*/
function getOrderBookKeys(uint _start) public view returns (uint[10] keys) {
for (uint i = 0; i < 10; i++) {
if (i + _start < orderBook.size()) {
keys[i] = orderBook.getKey(_start + i);
} else {
keys[i] = 0;
}
}
return keys;
}
/**
@dev Get the orderbook size to allow for batch fetching of keys
*/
function getOrderBookSize() public view returns (uint) {
return orderBook.size();
}
/**
@dev Verify that the number being passed fits into 111 bits for packing
@param _val The value to check
*/
function is111bit(uint _val) private pure returns (bool) {
return (_val < 1 << 111);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment