-
Notifications
You must be signed in to change notification settings - Fork 5
/
mica.sol
337 lines (251 loc) · 10.9 KB
/
mica.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
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
pragma solidity ^0.4.12;
import "github.com/JonnyLatte/MiscSolidity/SafeMath.sol";
import "github.com/JonnyLatte/MiscSolidity/erc20.sol";
//-----------------------------------------------------------------------------------------------------
//
// Mica, ERC20 token OTC exchange.
//
// JonnyLatte (c) 2017 The MIT License.
//
//-----------------------------------------------------------------------------------------------------
contract MicaTypes {
struct OFFER {
address owner; // owner of the offer
ERC20 currency; // "currency" refers to the token used to buy with
ERC20 asset; // "asset" refers to the token that is on offer
uint units; // asset is sold in multiples of "units"
uint price; // amount of currency needed to buy a lot of units
uint balance; // amount of asset belonging to this offfer
}
bool constant NEXT = true;
bool constant PREV = false;
}
contract Mica is MicaTypes
{
using SafeMath for uint;
// track UpdateEvent for any change in the status of an offer
event UpdateEvent(uint offer_index, address indexed currency, address indexed asset);
// track FillEvent for just fecthing trades
event FillEvent(
address indexed currency_token,
address indexed asset_token,
uint currency_value,
uint asset_value,
address seller,
address buyer);
// offers stores all offer data in one big int array
// the index to each offer never changes
// last_offer_index will be the index of the next offer created
// (incremented by one each time)
// offer data is retrieved with getOfferInfo()
uint public last_offer_index;
mapping(uint => OFFER) offers;
// firstOffer contains the index to the first offer of a given currency pair
mapping(address => mapping(address => uint)) public firstOffer;
// link stores a linked list, data values are key values so must be unique
// in this case the key is the offer index
// each link points to the next offer in the same currency pair
mapping(uint => mapping(bool => uint)) public link;
// user_offers maps to offers for a given address. Offer index values are not removed from this mapping
// last_user_offer_index is the number of offers created by a given address and the position in the mapping of the last one
mapping(address => uint) public last_user_offer_index;
mapping(address => mapping(uint => uint)) public user_offers;
// increment and get offer index
function nextOfferIndex() internal returns (uint) {
return ++last_offer_index;
}
//increment storage position for user offer index values
function nextUserOfferIndex(address user) internal returns (uint) {
return ++last_user_offer_index[user];
}
// create an offer
// removing balance amount of asset (the token being sold) from the sender
// contracts wanting to callMake offer and know the resulting offer index can get the value of last_offer_index
// after the call is made and before any subsiquent offer creation
function makeOffer(
ERC20 currency,
ERC20 asset,
uint units,
uint price,
uint balance
) returns (bool ok)
{
if(units == 0) throw;
if(price == 0) throw;
if(currency == address(0)) throw;
if(asset == currency) throw;
if(balance < units) throw;
if(!asset.transferFrom(msg.sender,this,balance)) throw;
OFFER memory offer;
offer.owner = msg.sender;
offer.currency = currency;
offer.asset = asset;
offer.units = units;
offer.balance = balance;
offer.price = price;
uint offerIndex = nextOfferIndex();
offers[offerIndex] = offer;
// create list for specific currency pair
link[offerIndex][NEXT] = firstOffer[currency][asset];
link[firstOffer[currency][asset]][PREV] = offerIndex;
firstOffer[currency][asset] = offerIndex;
UpdateEvent(offerIndex, currency, asset);
// record offer for indexing by user
user_offers[msg.sender][nextUserOfferIndex(msg.sender)] = offerIndex;
return true;
}
// taker fills offer, removing it if filled
function buy(uint offer_index, uint asset_amount_to_buy) returns (bool ok)
{
OFFER offer = offers[offer_index];
if(offer.units == 0) return; // expired offer has zero units
uint unit_lots_to_buy = asset_amount_to_buy.safeDiv(offer.units).min(offer.balance.safeDiv(offer.units));
if(unit_lots_to_buy == 0) return false;
uint currency_value = unit_lots_to_buy.safeMul(offer.price);
uint asset_value = unit_lots_to_buy.safeMul(offer.units);
if(currency_value < unit_lots_to_buy) throw; //overflow test
if(asset_value < unit_lots_to_buy) throw; //overflow test
offers[offer_index].balance = offers[offer_index].balance.safeSub(asset_value);
if(!offer.currency.transferFrom(msg.sender,offer.owner,currency_value)) throw;
if(!offer.asset.transfer(msg.sender,asset_value)) throw;
if(offer.balance < offer.units) {
internalCancelOffer(offer_index);
}
UpdateEvent(offer_index, offer.currency, offer.asset);
FillEvent(offer.currency, offer.asset,currency_value,asset_value,offer.owner,msg.sender);
return true;
}
// remove offer index from list of offers associated with each currency pair
function internalUnlink(uint offer_index, address currency, address asset) internal {
link[link[offer_index][PREV]][NEXT] = link[offer_index][NEXT];
link[link[offer_index][NEXT]][PREV] = link[offer_index][PREV];
if( firstOffer[currency][asset] == offer_index) {
firstOffer[currency][asset] = link[offer_index][NEXT];
}
}
// internalCancelOffer()
// called by cancelOffer to cancel an offer
// and buy() when an order has less funds than can be sold (dust is returned to owner)
function internalCancelOffer(uint offer_index) internal
{
OFFER offer = offers[ offer_index];
internalUnlink( offer_index,offer.currency,offer.asset);
UpdateEvent(offer_index, offer.currency, offer.asset);
if(offer.balance > 0) {
if(!offer.asset.transfer(offer.owner,offer.balance)) throw;
}
delete offers[offer_index];
}
// Cancel an offer returning funds to owner
function cancelOffer(uint offer_index) returns (bool ok)
{
if(msg.sender == offers[offer_index].owner) {
internalCancelOffer(offer_index);
UpdateEvent(offer_index, offers[offer_index].currency, offers[offer_index].asset);
return true;
}
return false;
}
// Add additional funds to offer
function fundOffer(uint offer_index, uint additional_balance) returns (bool ok)
{
if(msg.sender == offers[offer_index].owner)
{
if(!offers[offer_index].asset.transferFrom(msg.sender,this,additional_balance)) throw;
offers[offer_index].balance = offers[offer_index].balance + additional_balance;
if(offers[offer_index].balance < additional_balance) throw; // overflow check
UpdateEvent(offer_index, offers[offer_index].currency, offers[offer_index].asset);
return true;
}
return false;
}
// Returns information about an offer offer_index
// next will be an order index of the same pair
function getOfferInfo(uint offer_index) constant returns (
uint index,
address owner,
ERC20 currency,
ERC20 asset,
uint units,
uint price,
uint balance,
uint next
)
{
index = offer_index;
OFFER offer = offers[offer_index];
owner = offer.owner;
asset = offer.asset;
currency = offer.currency;
units = offer.units;
price = offer.price;
balance = offer.balance;
next = link[offer_index][NEXT];
}
}
// MicaHelper: pull specific data from Mica contract
// This could be included in Mica itself but is seperate to keep the Mica contract simple
contract MicaHelper is MicaTypes
{
using SafeMath for uint;
function countOffersFrom(Mica mica, uint offer_index) constant returns (uint) {
if(offer_index == 0) return 0;
return 1 + countOffersFrom(mica,mica.link(offer_index,NEXT));
}
function countOffers(Mica mica, address currency, address asset) constant returns (uint count) {
count = countOffersFrom(mica, mica.firstOffer(currency,asset));
}
function getMarketOffer(Mica mica, address _currency, address _asset, uint _pos) constant returns (
uint index,
address owner,
ERC20 currency,
ERC20 asset,
uint units,
uint price,
uint balance,
uint next
)
{
uint offer_index = mica.firstOffer(_currency,_asset);
while(_pos > 0 && offer_index != 0) {
offer_index = mica.link(offer_index,NEXT);
_pos--;
}
return mica.getOfferInfo(offer_index);
}
function getUserOffer(Mica mica, address _user, uint _pos) constant returns (
uint index,
address owner,
ERC20 currency,
ERC20 asset,
uint units,
uint price,
uint balance,
uint next
)
{
return mica.getOfferInfo(mica.user_offers(_user,_pos));
}
function bestOffer(Mica mica, address _currency, address _asset, uint minBalance) constant returns (uint best) {
uint offer_index = mica.firstOffer(_currency,_asset);
best = offer_index;
uint best_units;
uint best_price;
uint best_balance;
uint current_units;
uint current_price;
uint current_balance;
(,,,,best_units,best_price,best_balance,) = mica.getOfferInfo(offer_index);
offer_index = mica.link(offer_index,NEXT);
while(offer_index != 0) {
(,,,,current_units,current_price,current_balance,) = mica.getOfferInfo(offer_index);
if(current_price * best_units < best_units.safeMul(current_price) && current_balance >= minBalance) {
best = offer_index;
(best_units,best_price,best_balance) = (current_units,current_price,current_balance);
}
offer_index = mica.link(offer_index,NEXT);
}
if(best_balance < minBalance) best = 0;
return best;
}
}