-
Notifications
You must be signed in to change notification settings - Fork 3
/
RubiconMarket.sol
1276 lines (1107 loc) · 41.8 KB
/
RubiconMarket.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
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/// SPDX-License-Identifier: Apache-2.0
/// This contract is a derivative work of the open-source work of Oasis DEX: https://github.com/OasisDEX/oasis
/// @title RubiconMarket.sol
/// @notice Please see the repository for this code at https://github.com/RubiconDeFi/rubicon-protocol-v1;
pragma solidity =0.7.6;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
/// @notice DSAuth events for authentication schema
contract DSAuthEvents {
event LogSetAuthority(address indexed authority);
event LogSetOwner(address indexed owner);
}
/// @notice DSAuth library for setting owner of the contract
/// @dev Provides the auth modifier for authenticated function calls
contract DSAuth is DSAuthEvents {
address public owner;
function setOwner(address owner_) external auth {
owner = owner_;
emit LogSetOwner(owner);
}
modifier auth() {
require(isAuthorized(msg.sender), "ds-auth-unauthorized");
_;
}
function isAuthorized(address src) internal view returns (bool) {
if (src == address(this)) {
return true;
} else if (src == owner) {
return true;
} else {
return false;
}
}
}
/// @notice DSMath library for safe math without integer overflow/underflow
contract DSMath {
function add(uint256 x, uint256 y) internal pure returns (uint256 z) {
require((z = x + y) >= x, "ds-math-add-overflow");
}
function sub(uint256 x, uint256 y) internal pure returns (uint256 z) {
require((z = x - y) <= x, "ds-math-sub-underflow");
}
function mul(uint256 x, uint256 y) internal pure returns (uint256 z) {
require(y == 0 || (z = x * y) / y == x, "ds-math-mul-overflow");
}
function min(uint256 x, uint256 y) internal pure returns (uint256 z) {
return x <= y ? x : y;
}
function max(uint256 x, uint256 y) internal pure returns (uint256 z) {
return x >= y ? x : y;
}
function imin(int256 x, int256 y) internal pure returns (int256 z) {
return x <= y ? x : y;
}
function imax(int256 x, int256 y) internal pure returns (int256 z) {
return x >= y ? x : y;
}
uint256 constant WAD = 10**18;
uint256 constant RAY = 10**27;
function wmul(uint256 x, uint256 y) internal pure returns (uint256 z) {
z = add(mul(x, y), WAD / 2) / WAD;
}
function rmul(uint256 x, uint256 y) internal pure returns (uint256 z) {
z = add(mul(x, y), RAY / 2) / RAY;
}
function wdiv(uint256 x, uint256 y) internal pure returns (uint256 z) {
z = add(mul(x, WAD), y / 2) / y;
}
function rdiv(uint256 x, uint256 y) internal pure returns (uint256 z) {
z = add(mul(x, RAY), y / 2) / y;
}
}
// /// @notice ERC-20 interface as derived from EIP-20
// contract ERC20 {
// function totalSupply() public view returns (uint256);
// function balanceOf(address guy) public view returns (uint256);
// function allowance(address src, address guy) public view returns (uint256);
// function approve(address guy, uint256 wad) public returns (bool);
// function transfer(address dst, uint256 wad) public returns (bool);
// function transferFrom(
// address src,
// address dst,
// uint256 wad
// ) public returns (bool);
// }
/// @notice Events contract for logging trade activity on Rubicon Market
/// @dev Provides the key event logs that are used in all core functionality of exchanging on the Rubicon Market
contract EventfulMarket {
event LogItemUpdate(uint256 id);
event LogTrade(
uint256 pay_amt,
address indexed pay_gem,
uint256 buy_amt,
address indexed buy_gem
);
event LogMake(
bytes32 indexed id,
bytes32 indexed pair,
address indexed maker,
ERC20 pay_gem,
ERC20 buy_gem,
uint128 pay_amt,
uint128 buy_amt,
uint64 timestamp
);
event LogBump(
bytes32 indexed id,
bytes32 indexed pair,
address indexed maker,
ERC20 pay_gem,
ERC20 buy_gem,
uint128 pay_amt,
uint128 buy_amt,
uint64 timestamp
);
event LogTake(
bytes32 id,
bytes32 indexed pair,
address indexed maker,
ERC20 pay_gem,
ERC20 buy_gem,
address indexed taker,
uint128 take_amt,
uint128 give_amt,
uint64 timestamp
);
event LogKill(
bytes32 indexed id,
bytes32 indexed pair,
address indexed maker,
ERC20 pay_gem,
ERC20 buy_gem,
uint128 pay_amt,
uint128 buy_amt,
uint64 timestamp
);
event LogInt(string lol, uint256 input);
event FeeTake(
bytes32 indexed id,
bytes32 indexed pair,
ERC20 asset,
address indexed taker,
address feeTo,
uint256 feeAmt,
uint64 timestamp
);
event OfferDeleted(uint256 id);
}
/// @notice Core trading logic for ERC-20 pairs, an orderbook, and transacting of tokens
/// @dev This contract holds the core ERC-20 / ERC-20 offer, buy, and cancel logic
contract SimpleMarket is EventfulMarket, DSMath {
uint256 public last_offer_id;
/// @dev The mapping that makes up the core orderbook of the exchange
mapping(uint256 => OfferInfo) public offers;
bool locked;
/// @dev This parameter is in basis points
uint256 internal feeBPS;
/// @dev This parameter provides the address to which fees are sent
address internal feeTo;
struct OfferInfo {
uint256 pay_amt;
ERC20 pay_gem;
uint256 buy_amt;
ERC20 buy_gem;
address owner;
uint64 timestamp;
}
/// @notice Modifier that insures an order exists and is properly in the orderbook
modifier can_buy(uint256 id) virtual {
require(isActive(id));
_;
}
/// @notice Modifier that checks the user to make sure they own the offer and its valid before they attempt to cancel it
modifier can_cancel(uint256 id) virtual {
require(isActive(id));
require(getOwner(id) == msg.sender);
_;
}
modifier can_offer() virtual {
_;
}
modifier synchronized() {
require(!locked);
locked = true;
_;
locked = false;
}
function isActive(uint256 id) public view returns (bool active) {
return offers[id].timestamp > 0;
}
function getOwner(uint256 id) public view returns (address owner) {
return offers[id].owner;
}
function getOffer(uint256 id)
public
view
returns (
uint256,
ERC20,
uint256,
ERC20
)
{
OfferInfo memory _offer = offers[id];
return (_offer.pay_amt, _offer.pay_gem, _offer.buy_amt, _offer.buy_gem);
}
/// @notice Below are the main public entrypoints
function bump(bytes32 id_) external can_buy(uint256(id_)) {
uint256 id = uint256(id_);
emit LogBump(
id_,
keccak256(abi.encodePacked(offers[id].pay_gem, offers[id].buy_gem)),
offers[id].owner,
offers[id].pay_gem,
offers[id].buy_gem,
uint128(offers[id].pay_amt),
uint128(offers[id].buy_amt),
offers[id].timestamp
);
}
/// @notice Accept a given `quantity` of an offer. Transfers funds from caller/taker to offer maker, and from market to caller/taker.
/// @notice The fee for taker trades is paid in this function.
function buy(uint256 id, uint256 quantity)
public
virtual
can_buy(id)
synchronized
returns (bool)
{
OfferInfo memory _offer = offers[id];
uint256 spend = mul(quantity, _offer.buy_amt) / _offer.pay_amt;
require(uint128(spend) == spend, "spend is not an int");
require(uint128(quantity) == quantity, "quantity is not an int");
///@dev For backwards semantic compatibility.
if (
quantity == 0 ||
spend == 0 ||
quantity > _offer.pay_amt ||
spend > _offer.buy_amt
) {
return false;
}
// Fee logic added on taker trades
uint256 fee = mul(spend, feeBPS) / 10000;
require(
_offer.buy_gem.transferFrom(msg.sender, feeTo, fee),
"Insufficient funds to cover fee"
);
offers[id].pay_amt = sub(_offer.pay_amt, quantity);
offers[id].buy_amt = sub(_offer.buy_amt, spend);
require(
_offer.buy_gem.transferFrom(msg.sender, _offer.owner, spend),
"_offer.buy_gem.transferFrom(msg.sender, _offer.owner, spend) failed - check that you can pay the fee"
);
require(
_offer.pay_gem.transfer(msg.sender, quantity),
"_offer.pay_gem.transfer(msg.sender, quantity) failed"
);
emit LogItemUpdate(id);
emit LogTake(
bytes32(id),
keccak256(abi.encodePacked(_offer.pay_gem, _offer.buy_gem)),
_offer.owner,
_offer.pay_gem,
_offer.buy_gem,
msg.sender,
uint128(quantity),
uint128(spend),
uint64(block.timestamp)
);
emit FeeTake(
bytes32(id),
keccak256(abi.encodePacked(_offer.pay_gem, _offer.buy_gem)),
_offer.buy_gem,
msg.sender,
feeTo,
fee,
uint64(block.timestamp)
);
emit LogTrade(
quantity,
address(_offer.pay_gem),
spend,
address(_offer.buy_gem)
);
if (offers[id].pay_amt == 0) {
delete offers[id];
emit OfferDeleted(id);
}
return true;
}
/// @notice Allows the caller to cancel the offer if it is their own.
/// @notice This function refunds the offer to the maker.
function cancel(uint256 id)
public
virtual
can_cancel(id)
synchronized
returns (bool success)
{
OfferInfo memory _offer = offers[id];
delete offers[id];
require(_offer.pay_gem.transfer(_offer.owner, _offer.pay_amt));
emit LogItemUpdate(id);
emit LogKill(
bytes32(id),
keccak256(abi.encodePacked(_offer.pay_gem, _offer.buy_gem)),
_offer.owner,
_offer.pay_gem,
_offer.buy_gem,
uint128(_offer.pay_amt),
uint128(_offer.buy_amt),
uint64(block.timestamp)
);
success = true;
}
function kill(bytes32 id) external virtual {
require(cancel(uint256(id)));
}
function make(
ERC20 pay_gem,
ERC20 buy_gem,
uint128 pay_amt,
uint128 buy_amt
) external virtual returns (bytes32 id) {
return bytes32(offer(pay_amt, pay_gem, buy_amt, buy_gem));
}
/// @notice Key function to make a new offer. Takes funds from the caller into market escrow.
function offer(
uint256 pay_amt,
ERC20 pay_gem,
uint256 buy_amt,
ERC20 buy_gem
) public virtual can_offer synchronized returns (uint256 id) {
require(uint128(pay_amt) == pay_amt);
require(uint128(buy_amt) == buy_amt);
require(pay_amt > 0);
require(pay_gem != ERC20(0x0));
require(buy_amt > 0);
require(buy_gem != ERC20(0x0));
require(pay_gem != buy_gem);
OfferInfo memory info;
info.pay_amt = pay_amt;
info.pay_gem = pay_gem;
info.buy_amt = buy_amt;
info.buy_gem = buy_gem;
info.owner = msg.sender;
info.timestamp = uint64(block.timestamp);
id = _next_id();
offers[id] = info;
require(pay_gem.transferFrom(msg.sender, address(this), pay_amt));
emit LogItemUpdate(id);
emit LogMake(
bytes32(id),
keccak256(abi.encodePacked(pay_gem, buy_gem)),
msg.sender,
pay_gem,
buy_gem,
uint128(pay_amt),
uint128(buy_amt),
uint64(block.timestamp)
);
}
function take(bytes32 id, uint128 maxTakeAmount) external virtual {
require(buy(uint256(id), maxTakeAmount));
}
function _next_id() internal returns (uint256) {
last_offer_id++;
return last_offer_id;
}
// Fee logic
function getFeeBPS() internal view returns (uint256) {
return feeBPS;
}
}
/// @notice Expiring market is a Simple Market with a market lifetime.
/// @dev When the close_time has been reached, offers can only be cancelled (offer and buy will throw).
contract ExpiringMarket is DSAuth, SimpleMarket {
bool public stopped;
/// @dev After close_time has been reached, no new offers are allowed.
modifier can_offer() override {
require(!isClosed());
_;
}
/// @dev After close, no new buys are allowed.
modifier can_buy(uint256 id) override {
require(isActive(id));
require(!isClosed());
_;
}
/// @dev After close, anyone can cancel an offer.
modifier can_cancel(uint256 id) virtual override {
require(isActive(id));
require((msg.sender == getOwner(id)) || isClosed());
_;
}
function isClosed() public pure returns (bool closed) {
return false;
}
function getTime() public view returns (uint64) {
return uint64(block.timestamp);
}
function stop() external auth {
stopped = true;
}
}
contract DSNote {
event LogNote(
bytes4 indexed sig,
address indexed guy,
bytes32 indexed foo,
bytes32 indexed bar,
uint256 wad,
bytes fax
) anonymous;
modifier note() {
bytes32 foo;
bytes32 bar;
uint256 wad;
assembly {
foo := calldataload(4)
bar := calldataload(36)
wad := callvalue()
}
emit LogNote(msg.sig, msg.sender, foo, bar, wad, msg.data);
_;
}
}
contract MatchingEvents {
event LogBuyEnabled(bool isEnabled);
event LogMinSell(address pay_gem, uint256 min_amount);
event LogMatchingEnabled(bool isEnabled);
event LogUnsortedOffer(uint256 id);
event LogSortedOffer(uint256 id);
event LogInsert(address keeper, uint256 id);
event LogDelete(address keeper, uint256 id);
event LogMatch(uint256 id, uint256 amount);
}
/// @notice The core Rubicon Market smart contract
/// @notice This contract is based on the original open-source work done by OasisDEX under the Apache License 2.0
/// @dev This contract inherits the key trading functionality from SimpleMarket
contract RubiconMarket is MatchingEvents, ExpiringMarket, DSNote {
bool public buyEnabled = true; //buy enabled
bool public matchingEnabled = true; //true: enable matching,
//false: revert to expiring market
/// @dev Below is variable to allow for a proxy-friendly constructor
bool public initialized;
/// @dev unused deprecated variable for applying a token distribution on top of a trade
bool public AqueductDistributionLive;
/// @dev unused deprecated variable for applying a token distribution of this token on top of a trade
address public AqueductAddress;
struct sortInfo {
uint256 next; //points to id of next higher offer
uint256 prev; //points to id of previous lower offer
uint256 delb; //the blocknumber where this entry was marked for delete
}
mapping(uint256 => sortInfo) public _rank; //doubly linked lists of sorted offer ids
mapping(address => mapping(address => uint256)) public _best; //id of the highest offer for a token pair
mapping(address => mapping(address => uint256)) public _span; //number of offers stored for token pair in sorted orderbook
mapping(address => uint256) public _dust; //minimum sell amount for a token to avoid dust offers
mapping(uint256 => uint256) public _near; //next unsorted offer id
uint256 public _head; //first unsorted offer id
uint256 public dustId; // id of the latest offer marked as dust
/// @dev Proxy-safe initialization of storage
function initialize(bool _live, address _feeTo) public {
require(!initialized, "contract is already initialized");
AqueductDistributionLive = _live;
/// @notice The market fee recipient
feeTo = _feeTo;
owner = msg.sender;
emit LogSetOwner(msg.sender);
/// @notice The starting fee on taker trades in basis points
feeBPS = 20;
initialized = true;
matchingEnabled = true;
buyEnabled = true;
}
// After close, anyone can cancel an offer
modifier can_cancel(uint256 id) override {
require(isActive(id), "Offer was deleted or taken, or never existed.");
require(
isClosed() || msg.sender == getOwner(id) || id == dustId,
"Offer can not be cancelled because user is not owner, and market is open, and offer sells required amount of tokens."
);
_;
}
// ---- Public entrypoints ---- //
function make(
ERC20 pay_gem,
ERC20 buy_gem,
uint128 pay_amt,
uint128 buy_amt
) public override returns (bytes32) {
return bytes32(offer(pay_amt, pay_gem, buy_amt, buy_gem));
}
function take(bytes32 id, uint128 maxTakeAmount) public override {
require(buy(uint256(id), maxTakeAmount));
}
function kill(bytes32 id) external override {
require(cancel(uint256(id)));
}
// Make a new offer. Takes funds from the caller into market escrow.
//
// If matching is enabled:
// * creates new offer without putting it in
// the sorted list.
// * available to authorized contracts only!
// * keepers should call insert(id,pos)
// to put offer in the sorted list.
//
// If matching is disabled:
// * calls expiring market's offer().
// * available to everyone without authorization.
// * no sorting is done.
//
function offer(
uint256 pay_amt, //maker (ask) sell how much
ERC20 pay_gem, //maker (ask) sell which token
uint256 buy_amt, //taker (ask) buy how much
ERC20 buy_gem //taker (ask) buy which token
) public override returns (uint256) {
require(!locked, "Reentrancy attempt");
function(uint256, ERC20, uint256, ERC20) returns (uint256) fn
= matchingEnabled ? _offeru : super.offer;
return fn(pay_amt, pay_gem, buy_amt, buy_gem);
}
// Make a new offer. Takes funds from the caller into market escrow.
function offer(
uint256 pay_amt, //maker (ask) sell how much
ERC20 pay_gem, //maker (ask) sell which token
uint256 buy_amt, //maker (ask) buy how much
ERC20 buy_gem, //maker (ask) buy which token
uint256 pos //position to insert offer, 0 should be used if unknown
) external can_offer returns (uint256) {
return offer(pay_amt, pay_gem, buy_amt, buy_gem, pos, true);
}
function offer(
uint256 pay_amt, //maker (ask) sell how much
ERC20 pay_gem, //maker (ask) sell which token
uint256 buy_amt, //maker (ask) buy how much
ERC20 buy_gem, //maker (ask) buy which token
uint256 pos, //position to insert offer, 0 should be used if unknown
bool matching //match "close enough" orders?
) public can_offer returns (uint256) {
require(!locked, "Reentrancy attempt");
require(_dust[address(pay_gem)] <= pay_amt);
if (matchingEnabled) {
return _matcho(pay_amt, pay_gem, buy_amt, buy_gem, pos, matching);
}
return super.offer(pay_amt, pay_gem, buy_amt, buy_gem);
}
//Transfers funds from caller to offer maker, and from market to caller.
function buy(uint256 id, uint256 amount)
public
override
can_buy(id)
returns (bool)
{
require(!locked, "Reentrancy attempt");
//Optional distribution on trade
if (AqueductDistributionLive) {
IAqueduct(AqueductAddress).distributeToMakerAndTaker(
getOwner(id),
msg.sender
);
}
function(uint256, uint256) returns (bool) fn = matchingEnabled
? _buys
: super.buy;
return fn(id, amount);
}
// Cancel an offer. Refunds offer maker.
function cancel(uint256 id)
public
override
can_cancel(id)
returns (bool success)
{
require(!locked, "Reentrancy attempt");
if (matchingEnabled) {
if (isOfferSorted(id)) {
require(_unsort(id));
} else {
require(_hide(id));
}
}
return super.cancel(id); //delete the offer.
}
//insert offer into the sorted list
//keepers need to use this function
function insert(
uint256 id, //maker (ask) id
uint256 pos //position to insert into
) public returns (bool) {
require(!locked, "Reentrancy attempt");
require(!isOfferSorted(id)); //make sure offers[id] is not yet sorted
require(isActive(id)); //make sure offers[id] is active
_hide(id); //remove offer from unsorted offers list
_sort(id, pos); //put offer into the sorted offers list
emit LogInsert(msg.sender, id);
return true;
}
//deletes _rank [id]
// Function should be called by keepers.
function del_rank(uint256 id) external returns (bool) {
require(!locked, "Reentrancy attempt");
require(
!isActive(id) &&
_rank[id].delb != 0 &&
_rank[id].delb < block.number - 10
);
delete _rank[id];
emit LogDelete(msg.sender, id);
return true;
}
//set the minimum sell amount for a token
// Function is used to avoid "dust offers" that have
// very small amount of tokens to sell, and it would
// cost more gas to accept the offer, than the value
// of tokens received.
function setMinSell(
ERC20 pay_gem, //token to assign minimum sell amount to
uint256 dust //maker (ask) minimum sell amount
) external auth note returns (bool) {
_dust[address(pay_gem)] = dust;
emit LogMinSell(address(pay_gem), dust);
return true;
}
//returns the minimum sell amount for an offer
function getMinSell(
ERC20 pay_gem //token for which minimum sell amount is queried
) external view returns (uint256) {
return _dust[address(pay_gem)];
}
//set buy functionality enabled/disabled
function setBuyEnabled(bool buyEnabled_) external auth returns (bool) {
buyEnabled = buyEnabled_;
emit LogBuyEnabled(buyEnabled);
return true;
}
//set matching enabled/disabled
// If matchingEnabled true(default), then inserted offers are matched.
// Except the ones inserted by contracts, because those end up
// in the unsorted list of offers, that must be later sorted by
// keepers using insert().
// If matchingEnabled is false then RubiconMarket is reverted to ExpiringMarket,
// and matching is not done, and sorted lists are disabled.
function setMatchingEnabled(bool matchingEnabled_)
external
auth
returns (bool)
{
matchingEnabled = matchingEnabled_;
emit LogMatchingEnabled(matchingEnabled);
return true;
}
//return the best offer for a token pair
// the best offer is the lowest one if it's an ask,
// and highest one if it's a bid offer
function getBestOffer(ERC20 sell_gem, ERC20 buy_gem)
public
view
returns (uint256)
{
return _best[address(sell_gem)][address(buy_gem)];
}
//return the next worse offer in the sorted list
// the worse offer is the higher one if its an ask,
// a lower one if its a bid offer,
// and in both cases the newer one if they're equal.
function getWorseOffer(uint256 id) public view returns (uint256) {
return _rank[id].prev;
}
//return the next better offer in the sorted list
// the better offer is in the lower priced one if its an ask,
// the next higher priced one if its a bid offer
// and in both cases the older one if they're equal.
function getBetterOffer(uint256 id) external view returns (uint256) {
return _rank[id].next;
}
//return the amount of better offers for a token pair
function getOfferCount(ERC20 sell_gem, ERC20 buy_gem)
public
view
returns (uint256)
{
return _span[address(sell_gem)][address(buy_gem)];
}
//get the first unsorted offer that was inserted by a contract
// Contracts can't calculate the insertion position of their offer because it is not an O(1) operation.
// Their offers get put in the unsorted list of offers.
// Keepers can calculate the insertion position offchain and pass it to the insert() function to insert
// the unsorted offer into the sorted list. Unsorted offers will not be matched, but can be bought with buy().
function getFirstUnsortedOffer() public view returns (uint256) {
return _head;
}
//get the next unsorted offer
// Can be used to cycle through all the unsorted offers.
function getNextUnsortedOffer(uint256 id) public view returns (uint256) {
return _near[id];
}
function isOfferSorted(uint256 id) public view returns (bool) {
return
_rank[id].next != 0 ||
_rank[id].prev != 0 ||
_best[address(offers[id].pay_gem)][address(offers[id].buy_gem)] ==
id;
}
function sellAllAmount(
ERC20 pay_gem,
uint256 pay_amt,
ERC20 buy_gem,
uint256 min_fill_amount
) external returns (uint256 fill_amt) {
require(!locked, "Reentrancy attempt");
uint256 offerId;
while (pay_amt > 0) {
//while there is amount to sell
offerId = getBestOffer(buy_gem, pay_gem); //Get the best offer for the token pair
require(offerId != 0); //Fails if there are not more offers
// There is a chance that pay_amt is smaller than 1 wei of the other token
if (
pay_amt * 1 ether <
wdiv(offers[offerId].buy_amt, offers[offerId].pay_amt)
) {
break; //We consider that all amount is sold
}
if (pay_amt >= offers[offerId].buy_amt) {
//If amount to sell is higher or equal than current offer amount to buy
fill_amt = add(fill_amt, offers[offerId].pay_amt); //Add amount bought to acumulator
pay_amt = sub(pay_amt, offers[offerId].buy_amt); //Decrease amount to sell
take(bytes32(offerId), uint128(offers[offerId].pay_amt)); //We take the whole offer
} else {
// if lower
uint256 baux = rmul(
pay_amt * 10**9,
rdiv(offers[offerId].pay_amt, offers[offerId].buy_amt)
) / 10**9;
fill_amt = add(fill_amt, baux); //Add amount bought to acumulator
take(bytes32(offerId), uint128(baux)); //We take the portion of the offer that we need
pay_amt = 0; //All amount is sold
}
}
require(fill_amt >= min_fill_amount);
}
function buyAllAmount(
ERC20 buy_gem,
uint256 buy_amt,
ERC20 pay_gem,
uint256 max_fill_amount
) external returns (uint256 fill_amt) {
require(!locked, "Reentrancy attempt");
uint256 offerId;
while (buy_amt > 0) {
//Meanwhile there is amount to buy
offerId = getBestOffer(buy_gem, pay_gem); //Get the best offer for the token pair
require(offerId != 0);
// There is a chance that buy_amt is smaller than 1 wei of the other token
if (
buy_amt * 1 ether <
wdiv(offers[offerId].pay_amt, offers[offerId].buy_amt)
) {
break; //We consider that all amount is sold
}
if (buy_amt >= offers[offerId].pay_amt) {
//If amount to buy is higher or equal than current offer amount to sell
fill_amt = add(fill_amt, offers[offerId].buy_amt); //Add amount sold to acumulator
buy_amt = sub(buy_amt, offers[offerId].pay_amt); //Decrease amount to buy
take(bytes32(offerId), uint128(offers[offerId].pay_amt)); //We take the whole offer
} else {
//if lower
fill_amt = add(
fill_amt,
rmul(
buy_amt * 10**9,
rdiv(offers[offerId].buy_amt, offers[offerId].pay_amt)
) / 10**9
); //Add amount sold to acumulator
take(bytes32(offerId), uint128(buy_amt)); //We take the portion of the offer that we need
buy_amt = 0; //All amount is bought
}
}
require(fill_amt <= max_fill_amount);
}
function getBuyAmount(
ERC20 buy_gem,
ERC20 pay_gem,
uint256 pay_amt
) external view returns (uint256 fill_amt) {
uint256 offerId = getBestOffer(buy_gem, pay_gem); //Get best offer for the token pair
while (pay_amt > offers[offerId].buy_amt) {
fill_amt = add(fill_amt, offers[offerId].pay_amt); //Add amount to buy accumulator
pay_amt = sub(pay_amt, offers[offerId].buy_amt); //Decrease amount to pay
if (pay_amt > 0) {
//If we still need more offers
offerId = getWorseOffer(offerId); //We look for the next best offer
require(offerId != 0); //Fails if there are not enough offers to complete
}
}
fill_amt = add(
fill_amt,
rmul(
pay_amt * 10**9,
rdiv(offers[offerId].pay_amt, offers[offerId].buy_amt)
) / 10**9
); //Add proportional amount of last offer to buy accumulator
}
function getPayAmount(
ERC20 pay_gem,
ERC20 buy_gem,
uint256 buy_amt
) external view returns (uint256 fill_amt) {
uint256 offerId = getBestOffer(buy_gem, pay_gem); //Get best offer for the token pair
while (buy_amt > offers[offerId].pay_amt) {
fill_amt = add(fill_amt, offers[offerId].buy_amt); //Add amount to pay accumulator
buy_amt = sub(buy_amt, offers[offerId].pay_amt); //Decrease amount to buy
if (buy_amt > 0) {
//If we still need more offers
offerId = getWorseOffer(offerId); //We look for the next best offer
require(offerId != 0); //Fails if there are not enough offers to complete
}
}
fill_amt = add(
fill_amt,
rmul(
buy_amt * 10**9,
rdiv(offers[offerId].buy_amt, offers[offerId].pay_amt)
) / 10**9
); //Add proportional amount of last offer to pay accumulator
}
// ---- Internal Functions ---- //
function _buys(uint256 id, uint256 amount) internal returns (bool) {
require(buyEnabled);
if (amount == offers[id].pay_amt) {
if (isOfferSorted(id)) {
//offers[id] must be removed from sorted list because all of it is bought
_unsort(id);
} else {
_hide(id);
}
}
require(super.buy(id, amount));
// If offer has become dust during buy, we cancel it
if (
isActive(id) &&
offers[id].pay_amt < _dust[address(offers[id].pay_gem)]
) {
dustId = id; //enable current msg.sender to call cancel(id)
cancel(id);
}
return true;
}
//find the id of the next higher offer after offers[id]
function _find(uint256 id) internal view returns (uint256) {
require(id > 0);
address buy_gem = address(offers[id].buy_gem);
address pay_gem = address(offers[id].pay_gem);
uint256 top = _best[pay_gem][buy_gem];
uint256 old_top = 0;
// Find the larger-than-id order whose successor is less-than-id.
while (top != 0 && _isPricedLtOrEq(id, top)) {
old_top = top;
top = _rank[top].prev;
}
return old_top;
}
//find the id of the next higher offer after offers[id]