This repository has been archived by the owner on May 7, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 2
/
ERC20Seller.sol
194 lines (164 loc) · 7.94 KB
/
ERC20Seller.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
/**
* https://contributing.kleros.io/smart-contract-workflow
* @reviewers: [unknownunknown1]
* @auditors: []
* @bounties: []
* @deployments: []
*/
/* solium-disable error-reason */
/* solium-disable security/no-block-members */
pragma solidity ^0.5.8;
import { SafeMath } from "openzeppelin-solidity/contracts/math/SafeMath.sol";
import { IERC20 } from "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol";
/**
* @title ERC20Seller
* @author Clément Lesaege - <clement@kleros.io>
* @dev A contract to allow the sale of tokens against ETH at a price decided by the seller.
* Note that this contract is kept as simple as possible.
* It is not optimized for handling a high amount of orders by the seller but to support 1-10 orders on average.
* If you intend to use a lot more simultaneous orders, we would suggest developing a heap-based version of this contract.
* Note that:
* - Orders are not automatically removed when they are completely fulfilled in order to avoid changing the orderID without intervention of the seller.
* - Tokens amounts and prices are supposed to be reasonable such that multiplying them does not overflow.
* - A few wei or basic token units may be lost in the process. Only use this contract to sell a token whose basic unit price is insignificant.
* - The token contract is trusted not to reenter during a call to this contract.
*/
contract ERC20Seller {
using SafeMath for uint256;
/* Storage */
address payable seller; // The party selling the tokens.
IERC20 public token; // The token to be sold.
uint public divisor; // The divisor of the token price. It is used to allow prices lower than 1 wei / basic_unit.
// A sell order.
struct Order {
uint price; // The selling price in wei * divisor / basic_unit.
uint amount; // The amount of token to sell in base unit.
}
Order[] public orders; // A list of orders.
/* Constant */
uint public MAX_ORDERS = 100; // The maximum amount of simultaneous orders. It is used to avoid having so much orders that the calls would run out of gas.
uint NO_ORDER_FOUND = uint(-1); // Value returned by findCheapestOrder when no orders are found.
uint MAX_VALUE = uint(-1); // Maximum value, such that it is never exceeded.
/* Events */
/**
* @dev Emitted when a contributor makes a purchase.
* @param _contributor The account that made the purchase.
* @param _amount The amount of tokens in basic units.
*/
event TokenPurchase(address _contributor, uint _amount);
/* Constructor */
/** @dev Constructs the seller contract.
* @param _token The token to sell.
* @param _divisor The divisor of the price.
*/
constructor(IERC20 _token, uint _divisor) public {
seller = msg.sender;
token = _token;
divisor = _divisor;
}
/* External */
/** @dev Add a sell order.
* @param _price The selling price in wei * divisor / basic_unit.
* @param _amount The amount of tokens to sell in basic units.
*/
function addOrder(uint _price, uint _amount) external {
require(msg.sender == seller, "Only the seller can perform this action.");
require(orders.length < MAX_ORDERS, "The maximum number of orders should not have already been reached.");
require(token.transferFrom(msg.sender, address(this), _amount));
orders.push(Order({price: _price, amount: _amount}));
}
/** @dev Increase the amount of an order.
* @param _orderID The ID of the order to increase the amount.
* @param _amount The amount of tokens to add to the total amount in basic units.
*/
function increaseAmount(uint _orderID, uint _amount) external {
require(msg.sender == seller, "Only the seller can perform this action.");
require(token.transferFrom(msg.sender, address(this), _amount));
orders[_orderID].amount = orders[_orderID].amount.add(_amount);
}
/** @dev Decrease the amount of an order.
* @param _orderID The ID of the order to decrease the amount.
* @param _amount The amount of tokens to remove from the total amount in base units. If it is higher than the amount, all the tokens will be removed.
*/
function decreaseAmount(uint _orderID, uint _amount) external {
require(msg.sender == seller, "Only the seller can perform this action.");
uint amountToDecrease = orders[_orderID].amount < _amount ? orders[_orderID].amount : _amount;
require(token.transfer(seller, amountToDecrease));
orders[_orderID].amount = orders[_orderID].amount.sub(amountToDecrease);
}
/** @dev Remove an order.
* @param _orderID The ID of the order to remove.
*/
function removeOrder(uint _orderID) external {
require(msg.sender == seller, "Only the seller can perform this action.");
require(token.transfer(seller, orders[_orderID].amount));
orders[_orderID] = orders[orders.length - 1];
--orders.length;
}
/** @dev Fallback function automatically buys all it can, no matter the price.
*/
function () external payable {
buy(MAX_VALUE);
}
/* Public */
/** @dev Buy all the tokens possible at _maxPrice or lower.
* This function is in O(n²), where n is the amount of orders.
* @param _maxPrice Maximum price to pay.
*/
function buy(uint _maxPrice) public payable {
uint remainingETH = msg.value;
uint cheapestOrder = findCheapestOrder();
uint tokensBought;
while(remainingETH!=0 && cheapestOrder!=NO_ORDER_FOUND && orders[cheapestOrder].price<=_maxPrice) { // Check if there is an order to take. Note that this will never revert due to short-circuit evaluation rules.
uint fullOrderValue = orders[cheapestOrder].price.mul(orders[cheapestOrder].amount).div(divisor);
if (fullOrderValue <= remainingETH) { // Take the whole order.
tokensBought = tokensBought.add(orders[cheapestOrder].amount);
remainingETH = remainingETH.sub(fullOrderValue);
orders[cheapestOrder].amount = 0;
cheapestOrder = findCheapestOrder();
} else { // Take the whole buy.
uint amountBought = remainingETH.mul(divisor).div(orders[cheapestOrder].price);
tokensBought = tokensBought.add(amountBought);
orders[cheapestOrder].amount = orders[cheapestOrder].amount.sub(amountBought);
remainingETH = 0;
}
}
require(token.transfer(msg.sender, tokensBought));
emit TokenPurchase(msg.sender, tokensBought);
if (remainingETH != 0)
msg.sender.transfer(remainingETH); // Send back the ETH not used.
seller.transfer(address(this).balance); // Send the ETH to the seller.
}
/* Views */
/** @dev Find the cheapest order. This function is in O(n), where n is the amount of orders.
* @return _orderID The ID of the cheapest order. NO_ORDER_FOUND if there are no orders.
*/
function findCheapestOrder() public view returns (uint _orderID) {
uint bestPrice = MAX_VALUE;
_orderID = NO_ORDER_FOUND;
for (uint i = 0; i < orders.length; ++i) {
if (orders[i].price<bestPrice && orders[i].amount!=0) {
bestPrice = orders[i].price;
_orderID = i;
}
}
}
/** @dev Fetch all open order IDs. This function is O(n) where n is the amount of orders.
* @return _orderIDs The IDs of all open orders.
*/
function getOpenOrders() external view returns (uint[] memory orderIDs) {
uint orderCount = 0;
for (uint i = 0; i < orders.length; i++) {
if (orders[i].amount > 0)
orderCount++;
}
orderIDs = new uint[](orderCount);
uint counter = 0;
for (uint j = 0; j < orders.length; j++) {
if (orders[j].amount > 0) {
orderIDs[counter] = j;
counter++;
}
}
}
}