-
Notifications
You must be signed in to change notification settings - Fork 104
/
ExchangeCore.sol
753 lines (643 loc) · 31.6 KB
/
ExchangeCore.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
/*
Decentralized digital asset exchange. Supports any digital asset that can be represented on the Ethereum blockchain (i.e. - transferred in an Ethereum transaction or sequence of transactions).
Let us suppose two agents interacting with a distributed ledger have utility functions preferencing certain states of that ledger over others.
Aiming to maximize their utility, these agents may construct with their utility functions along with the present ledger state a mapping of state transitions (transactions) to marginal utilities.
Any composite state transition with positive marginal utility for and enactable by the combined permissions of both agents thus is a mutually desirable trade, and the trustless
code execution provided by a distributed ledger renders the requisite atomicity trivial.
Relative to this model, this instantiation makes two concessions to practicality:
- State transition preferences are not matched directly but instead intermediated by a standard of tokenized value.
- A small fee can be charged in WYV for order settlement in an amount configurable by the frontend hosting the orderbook.
Solidity presently possesses neither a first-class functional typesystem nor runtime reflection (ABI encoding in Solidity), so we must be a bit clever in implementation and work at a lower level of abstraction than would be ideal.
We elect to utilize the following structure for the initial version of the protocol:
- Buy-side and sell-side orders each provide calldata (bytes) - for a sell-side order, the state transition for sale, for a buy-side order, the state transition to be bought.
Along with the calldata, orders provide `replacementPattern`: a bytemask indicating which bytes of the calldata can be changed (e.g. NFT destination address).
When a buy-side and sell-side order are matched, the desired calldatas are unified, masked with the bytemasks, and checked for agreement.
This alone is enough to implement common simple state transitions, such as "transfer my CryptoKitty to any address" or "buy any of this kind of nonfungible token".
- Orders (of either side) can optionally specify a static (no state modification) callback function, which receives configurable data along with the actual calldata as a parameter.
Although it requires some encoding acrobatics, this allows for arbitrary transaction validation functions.
For example, a buy-sider order could express the intent to buy any CryptoKitty with a particular set of characteristics (checked in the static call),
or a sell-side order could express the intent to sell any of three ENS names, but not two others.
Use of the EVM's STATICCALL opcode, added in Ethereum Metropolis, allows the static calldata to be safely specified separately and thus this kind of matching to happen correctly
- that is to say, wherever the two (transaction => bool) functions intersect.
Future protocol versions may improve upon this structure in capability or usability according to protocol user feedback demand, with upgrades enacted by the Wyvern DAO.
*/
pragma solidity 0.4.23;
import "openzeppelin-solidity/contracts/token/ERC20/ERC20.sol";
import "openzeppelin-solidity/contracts/math/SafeMath.sol";
import "openzeppelin-solidity/contracts/ownership/Ownable.sol";
import "../registry/ProxyRegistry.sol";
import "../registry/TokenTransferProxy.sol";
import "../registry/AuthenticatedProxy.sol";
import "../common/ArrayUtils.sol";
import "../common/ReentrancyGuarded.sol";
import "./SaleKindInterface.sol";
/**
* @title ExchangeCore
* @author Project Wyvern Developers
*/
contract ExchangeCore is ReentrancyGuarded, Ownable {
/* The token used to pay exchange fees. */
ERC20 public exchangeToken;
/* User registry. */
ProxyRegistry public registry;
/* Token transfer proxy. */
TokenTransferProxy public tokenTransferProxy;
/* Cancelled / finalized orders, by hash. */
mapping(bytes32 => bool) public cancelledOrFinalized;
/* Orders verified by on-chain approval (alternative to ECDSA signatures so that smart contracts can place orders directly). */
mapping(bytes32 => bool) public approvedOrders;
/* For split fee orders, minimum required protocol maker fee, in basis points. Paid to owner (who can change it). */
uint public minimumMakerProtocolFee = 0;
/* For split fee orders, minimum required protocol taker fee, in basis points. Paid to owner (who can change it). */
uint public minimumTakerProtocolFee = 0;
/* Recipient of protocol fees. */
address public protocolFeeRecipient;
/* Fee method: protocol fee or split fee. */
enum FeeMethod { ProtocolFee, SplitFee }
/* Inverse basis point. */
uint public constant INVERSE_BASIS_POINT = 10000;
/* An ECDSA signature. */
struct Sig {
/* v parameter */
uint8 v;
/* r parameter */
bytes32 r;
/* s parameter */
bytes32 s;
}
/* An order on the exchange. */
struct Order {
/* Exchange address, intended as a versioning mechanism. */
address exchange;
/* Order maker address. */
address maker;
/* Order taker address, if specified. */
address taker;
/* Maker relayer fee of the order, unused for taker order. */
uint makerRelayerFee;
/* Taker relayer fee of the order, or maximum taker fee for a taker order. */
uint takerRelayerFee;
/* Maker protocol fee of the order, unused for taker order. */
uint makerProtocolFee;
/* Taker protocol fee of the order, or maximum taker fee for a taker order. */
uint takerProtocolFee;
/* Order fee recipient or zero address for taker order. */
address feeRecipient;
/* Fee method (protocol token or split fee). */
FeeMethod feeMethod;
/* Side (buy/sell). */
SaleKindInterface.Side side;
/* Kind of sale. */
SaleKindInterface.SaleKind saleKind;
/* Target. */
address target;
/* HowToCall. */
AuthenticatedProxy.HowToCall howToCall;
/* Calldata. */
bytes calldata;
/* Calldata replacement pattern, or an empty byte array for no replacement. */
bytes replacementPattern;
/* Static call target, zero-address for no static call. */
address staticTarget;
/* Static call extra data. */
bytes staticExtradata;
/* Token used to pay for the order, or the zero-address as a sentinel value for Ether. */
address paymentToken;
/* Base price of the order (in paymentTokens). */
uint basePrice;
/* Auction extra parameter - minimum bid increment for English auctions, starting/ending price difference. */
uint extra;
/* Listing timestamp. */
uint listingTime;
/* Expiration timestamp - 0 for no expiry. */
uint expirationTime;
/* Order salt, used to prevent duplicate hashes. */
uint salt;
}
event OrderApprovedPartOne (bytes32 indexed hash, address exchange, address indexed maker, address taker, uint makerRelayerFee, uint takerRelayerFee, uint makerProtocolFee, uint takerProtocolFee, address indexed feeRecipient, FeeMethod feeMethod, SaleKindInterface.Side side, SaleKindInterface.SaleKind saleKind, address target);
event OrderApprovedPartTwo (bytes32 indexed hash, AuthenticatedProxy.HowToCall howToCall, bytes calldata, bytes replacementPattern, address staticTarget, bytes staticExtradata, address paymentToken, uint basePrice, uint extra, uint listingTime, uint expirationTime, uint salt, bool orderbookInclusionDesired);
event OrderCancelled (bytes32 indexed hash);
event OrdersMatched (bytes32 buyHash, bytes32 sellHash, address indexed maker, address indexed taker, uint price, bytes32 indexed metadata);
/**
* @dev Change the minimum maker fee paid to the protocol (owner only)
* @param newMinimumMakerProtocolFee New fee to set in basis points
*/
function changeMinimumMakerProtocolFee(uint newMinimumMakerProtocolFee)
public
onlyOwner
{
minimumMakerProtocolFee = newMinimumMakerProtocolFee;
}
/**
* @dev Change the minimum taker fee paid to the protocol (owner only)
* @param newMinimumTakerProtocolFee New fee to set in basis points
*/
function changeMinimumTakerProtocolFee(uint newMinimumTakerProtocolFee)
public
onlyOwner
{
minimumTakerProtocolFee = newMinimumTakerProtocolFee;
}
/**
* @dev Change the protocol fee recipient (owner only)
* @param newProtocolFeeRecipient New protocol fee recipient address
*/
function changeProtocolFeeRecipient(address newProtocolFeeRecipient)
public
onlyOwner
{
protocolFeeRecipient = newProtocolFeeRecipient;
}
/**
* @dev Transfer tokens
* @param token Token to transfer
* @param from Address to charge fees
* @param to Address to receive fees
* @param amount Amount of protocol tokens to charge
*/
function transferTokens(address token, address from, address to, uint amount)
internal
{
if (amount > 0) {
require(tokenTransferProxy.transferFrom(token, from, to, amount));
}
}
/**
* @dev Charge a fee in protocol tokens
* @param from Address to charge fees
* @param to Address to receive fees
* @param amount Amount of protocol tokens to charge
*/
function chargeProtocolFee(address from, address to, uint amount)
internal
{
transferTokens(exchangeToken, from, to, amount);
}
/**
* @dev Execute a STATICCALL (introduced with Ethereum Metropolis, non-state-modifying external call)
* @param target Contract to call
* @param calldata Calldata (appended to extradata)
* @param extradata Base data for STATICCALL (probably function selector and argument encoding)
* @return The result of the call (success or failure)
*/
function staticCall(address target, bytes memory calldata, bytes memory extradata)
public
view
returns (bool result)
{
bytes memory combined = new bytes(calldata.length + extradata.length);
uint index;
assembly {
index := add(combined, 0x20)
}
index = ArrayUtils.unsafeWriteBytes(index, extradata);
ArrayUtils.unsafeWriteBytes(index, calldata);
assembly {
result := staticcall(gas, target, add(combined, 0x20), mload(combined), mload(0x40), 0)
}
return result;
}
/**
* Calculate size of an order struct when tightly packed
*
* @param order Order to calculate size of
* @return Size in bytes
*/
function sizeOf(Order memory order)
internal
pure
returns (uint)
{
return ((0x14 * 7) + (0x20 * 9) + 4 + order.calldata.length + order.replacementPattern.length + order.staticExtradata.length);
}
/**
* @dev Hash an order, returning the canonical order hash, without the message prefix
* @param order Order to hash
* @return Hash of order
*/
function hashOrder(Order memory order)
internal
pure
returns (bytes32 hash)
{
/* Unfortunately abi.encodePacked doesn't work here, stack size constraints. */
uint size = sizeOf(order);
bytes memory array = new bytes(size);
uint index;
assembly {
index := add(array, 0x20)
}
index = ArrayUtils.unsafeWriteAddress(index, order.exchange);
index = ArrayUtils.unsafeWriteAddress(index, order.maker);
index = ArrayUtils.unsafeWriteAddress(index, order.taker);
index = ArrayUtils.unsafeWriteUint(index, order.makerRelayerFee);
index = ArrayUtils.unsafeWriteUint(index, order.takerRelayerFee);
index = ArrayUtils.unsafeWriteUint(index, order.makerProtocolFee);
index = ArrayUtils.unsafeWriteUint(index, order.takerProtocolFee);
index = ArrayUtils.unsafeWriteAddress(index, order.feeRecipient);
index = ArrayUtils.unsafeWriteUint8(index, uint8(order.feeMethod));
index = ArrayUtils.unsafeWriteUint8(index, uint8(order.side));
index = ArrayUtils.unsafeWriteUint8(index, uint8(order.saleKind));
index = ArrayUtils.unsafeWriteAddress(index, order.target);
index = ArrayUtils.unsafeWriteUint8(index, uint8(order.howToCall));
index = ArrayUtils.unsafeWriteBytes(index, order.calldata);
index = ArrayUtils.unsafeWriteBytes(index, order.replacementPattern);
index = ArrayUtils.unsafeWriteAddress(index, order.staticTarget);
index = ArrayUtils.unsafeWriteBytes(index, order.staticExtradata);
index = ArrayUtils.unsafeWriteAddress(index, order.paymentToken);
index = ArrayUtils.unsafeWriteUint(index, order.basePrice);
index = ArrayUtils.unsafeWriteUint(index, order.extra);
index = ArrayUtils.unsafeWriteUint(index, order.listingTime);
index = ArrayUtils.unsafeWriteUint(index, order.expirationTime);
index = ArrayUtils.unsafeWriteUint(index, order.salt);
assembly {
hash := keccak256(add(array, 0x20), size)
}
return hash;
}
/**
* @dev Hash an order, returning the hash that a client must sign, including the standard message prefix
* @param order Order to hash
* @return Hash of message prefix and order hash per Ethereum format
*/
function hashToSign(Order memory order)
internal
pure
returns (bytes32)
{
return keccak256("\x19Ethereum Signed Message:\n32", hashOrder(order));
}
/**
* @dev Assert an order is valid and return its hash
* @param order Order to validate
* @param sig ECDSA signature
*/
function requireValidOrder(Order memory order, Sig memory sig)
internal
view
returns (bytes32)
{
bytes32 hash = hashToSign(order);
require(validateOrder(hash, order, sig));
return hash;
}
/**
* @dev Validate order parameters (does *not* check signature validity)
* @param order Order to validate
*/
function validateOrderParameters(Order memory order)
internal
view
returns (bool)
{
/* Order must be targeted at this protocol version (this Exchange contract). */
if (order.exchange != address(this)) {
return false;
}
/* Order must possess valid sale kind parameter combination. */
if (!SaleKindInterface.validateParameters(order.saleKind, order.expirationTime)) {
return false;
}
/* If using the split fee method, order must have sufficient protocol fees. */
if (order.feeMethod == FeeMethod.SplitFee && (order.makerProtocolFee < minimumMakerProtocolFee || order.takerProtocolFee < minimumTakerProtocolFee)) {
return false;
}
return true;
}
/**
* @dev Validate a provided previously approved / signed order, hash, and signature.
* @param hash Order hash (already calculated, passed to avoid recalculation)
* @param order Order to validate
* @param sig ECDSA signature
*/
function validateOrder(bytes32 hash, Order memory order, Sig memory sig)
internal
view
returns (bool)
{
/* Not done in an if-conditional to prevent unnecessary ecrecover evaluation, which seems to happen even though it should short-circuit. */
/* Order must have valid parameters. */
if (!validateOrderParameters(order)) {
return false;
}
/* Order must have not been canceled or already filled. */
if (cancelledOrFinalized[hash]) {
return false;
}
/* Order authentication. Order must be either:
/* (a) previously approved */
if (approvedOrders[hash]) {
return true;
}
/* or (b) ECDSA-signed by maker. */
if (ecrecover(hash, sig.v, sig.r, sig.s) == order.maker) {
return true;
}
return false;
}
/**
* @dev Approve an order and optionally mark it for orderbook inclusion. Must be called by the maker of the order
* @param order Order to approve
* @param orderbookInclusionDesired Whether orderbook providers should include the order in their orderbooks
*/
function approveOrder(Order memory order, bool orderbookInclusionDesired)
internal
{
/* CHECKS */
/* Assert sender is authorized to approve order. */
require(msg.sender == order.maker);
/* Calculate order hash. */
bytes32 hash = hashToSign(order);
/* Assert order has not already been approved. */
require(!approvedOrders[hash]);
/* EFFECTS */
/* Mark order as approved. */
approvedOrders[hash] = true;
/* Log approval event. Must be split in two due to Solidity stack size limitations. */
{
emit OrderApprovedPartOne(hash, order.exchange, order.maker, order.taker, order.makerRelayerFee, order.takerRelayerFee, order.makerProtocolFee, order.takerProtocolFee, order.feeRecipient, order.feeMethod, order.side, order.saleKind, order.target);
}
{
emit OrderApprovedPartTwo(hash, order.howToCall, order.calldata, order.replacementPattern, order.staticTarget, order.staticExtradata, order.paymentToken, order.basePrice, order.extra, order.listingTime, order.expirationTime, order.salt, orderbookInclusionDesired);
}
}
/**
* @dev Cancel an order, preventing it from being matched. Must be called by the maker of the order
* @param order Order to cancel
* @param sig ECDSA signature
*/
function cancelOrder(Order memory order, Sig memory sig)
internal
{
/* CHECKS */
/* Calculate order hash. */
bytes32 hash = requireValidOrder(order, sig);
/* Assert sender is authorized to cancel order. */
require(msg.sender == order.maker);
/* EFFECTS */
/* Mark order as cancelled, preventing it from being matched. */
cancelledOrFinalized[hash] = true;
/* Log cancel event. */
emit OrderCancelled(hash);
}
/**
* @dev Calculate the current price of an order (convenience function)
* @param order Order to calculate the price of
* @return The current price of the order
*/
function calculateCurrentPrice (Order memory order)
internal
view
returns (uint)
{
return SaleKindInterface.calculateFinalPrice(order.side, order.saleKind, order.basePrice, order.extra, order.listingTime, order.expirationTime);
}
/**
* @dev Calculate the price two orders would match at, if in fact they would match (otherwise fail)
* @param buy Buy-side order
* @param sell Sell-side order
* @return Match price
*/
function calculateMatchPrice(Order memory buy, Order memory sell)
view
internal
returns (uint)
{
/* Calculate sell price. */
uint sellPrice = SaleKindInterface.calculateFinalPrice(sell.side, sell.saleKind, sell.basePrice, sell.extra, sell.listingTime, sell.expirationTime);
/* Calculate buy price. */
uint buyPrice = SaleKindInterface.calculateFinalPrice(buy.side, buy.saleKind, buy.basePrice, buy.extra, buy.listingTime, buy.expirationTime);
/* Require price cross. */
require(buyPrice >= sellPrice);
/* Maker/taker priority. */
return sell.feeRecipient != address(0) ? sellPrice : buyPrice;
}
/**
* @dev Execute all ERC20 token / Ether transfers associated with an order match (fees and buyer => seller transfer)
* @param buy Buy-side order
* @param sell Sell-side order
*/
function executeFundsTransfer(Order memory buy, Order memory sell)
internal
returns (uint)
{
/* Only payable in the special case of unwrapped Ether. */
if (sell.paymentToken != address(0)) {
require(msg.value == 0);
}
/* Calculate match price. */
uint price = calculateMatchPrice(buy, sell);
/* If paying using a token (not Ether), transfer tokens. This is done prior to fee payments to that a seller will have tokens before being charged fees. */
if (price > 0 && sell.paymentToken != address(0)) {
transferTokens(sell.paymentToken, buy.maker, sell.maker, price);
}
/* Amount that will be received by seller (for Ether). */
uint receiveAmount = price;
/* Amount that must be sent by buyer (for Ether). */
uint requiredAmount = price;
/* Determine maker/taker and charge fees accordingly. */
if (sell.feeRecipient != address(0)) {
/* Sell-side order is maker. */
/* Assert taker fee is less than or equal to maximum fee specified by buyer. */
require(sell.takerRelayerFee <= buy.takerRelayerFee);
if (sell.feeMethod == FeeMethod.SplitFee) {
/* Assert taker fee is less than or equal to maximum fee specified by buyer. */
require(sell.takerProtocolFee <= buy.takerProtocolFee);
/* Maker fees are deducted from the token amount that the maker receives. Taker fees are extra tokens that must be paid by the taker. */
if (sell.makerRelayerFee > 0) {
uint makerRelayerFee = SafeMath.div(SafeMath.mul(sell.makerRelayerFee, price), INVERSE_BASIS_POINT);
if (sell.paymentToken == address(0)) {
receiveAmount = SafeMath.sub(receiveAmount, makerRelayerFee);
sell.feeRecipient.transfer(makerRelayerFee);
} else {
transferTokens(sell.paymentToken, sell.maker, sell.feeRecipient, makerRelayerFee);
}
}
if (sell.takerRelayerFee > 0) {
uint takerRelayerFee = SafeMath.div(SafeMath.mul(sell.takerRelayerFee, price), INVERSE_BASIS_POINT);
if (sell.paymentToken == address(0)) {
requiredAmount = SafeMath.add(requiredAmount, takerRelayerFee);
sell.feeRecipient.transfer(takerRelayerFee);
} else {
transferTokens(sell.paymentToken, buy.maker, sell.feeRecipient, takerRelayerFee);
}
}
if (sell.makerProtocolFee > 0) {
uint makerProtocolFee = SafeMath.div(SafeMath.mul(sell.makerProtocolFee, price), INVERSE_BASIS_POINT);
if (sell.paymentToken == address(0)) {
receiveAmount = SafeMath.sub(receiveAmount, makerProtocolFee);
protocolFeeRecipient.transfer(makerProtocolFee);
} else {
transferTokens(sell.paymentToken, sell.maker, protocolFeeRecipient, makerProtocolFee);
}
}
if (sell.takerProtocolFee > 0) {
uint takerProtocolFee = SafeMath.div(SafeMath.mul(sell.takerProtocolFee, price), INVERSE_BASIS_POINT);
if (sell.paymentToken == address(0)) {
requiredAmount = SafeMath.add(requiredAmount, takerProtocolFee);
protocolFeeRecipient.transfer(takerProtocolFee);
} else {
transferTokens(sell.paymentToken, buy.maker, protocolFeeRecipient, takerProtocolFee);
}
}
} else {
/* Charge maker fee to seller. */
chargeProtocolFee(sell.maker, sell.feeRecipient, sell.makerRelayerFee);
/* Charge taker fee to buyer. */
chargeProtocolFee(buy.maker, sell.feeRecipient, sell.takerRelayerFee);
}
} else {
/* Buy-side order is maker. */
/* Assert taker fee is less than or equal to maximum fee specified by seller. */
require(buy.takerRelayerFee <= sell.takerRelayerFee);
if (sell.feeMethod == FeeMethod.SplitFee) {
/* The Exchange does not escrow Ether, so direct Ether can only be used to with sell-side maker / buy-side taker orders. */
require(sell.paymentToken != address(0));
/* Assert taker fee is less than or equal to maximum fee specified by seller. */
require(buy.takerProtocolFee <= sell.takerProtocolFee);
if (buy.makerRelayerFee > 0) {
makerRelayerFee = SafeMath.div(SafeMath.mul(buy.makerRelayerFee, price), INVERSE_BASIS_POINT);
transferTokens(sell.paymentToken, buy.maker, buy.feeRecipient, makerRelayerFee);
}
if (buy.takerRelayerFee > 0) {
takerRelayerFee = SafeMath.div(SafeMath.mul(buy.takerRelayerFee, price), INVERSE_BASIS_POINT);
transferTokens(sell.paymentToken, sell.maker, buy.feeRecipient, takerRelayerFee);
}
if (buy.makerProtocolFee > 0) {
makerProtocolFee = SafeMath.div(SafeMath.mul(buy.makerProtocolFee, price), INVERSE_BASIS_POINT);
transferTokens(sell.paymentToken, buy.maker, protocolFeeRecipient, makerProtocolFee);
}
if (buy.takerProtocolFee > 0) {
takerProtocolFee = SafeMath.div(SafeMath.mul(buy.takerProtocolFee, price), INVERSE_BASIS_POINT);
transferTokens(sell.paymentToken, sell.maker, protocolFeeRecipient, takerProtocolFee);
}
} else {
/* Charge maker fee to buyer. */
chargeProtocolFee(buy.maker, buy.feeRecipient, buy.makerRelayerFee);
/* Charge taker fee to seller. */
chargeProtocolFee(sell.maker, buy.feeRecipient, buy.takerRelayerFee);
}
}
if (sell.paymentToken == address(0)) {
/* Special-case Ether, order must be matched by buyer. */
require(msg.value >= requiredAmount);
sell.maker.transfer(receiveAmount);
/* Allow overshoot for variable-price auctions, refund difference. */
uint diff = SafeMath.sub(msg.value, requiredAmount);
if (diff > 0) {
buy.maker.transfer(diff);
}
}
/* This contract should never hold Ether, however, we cannot assert this, since it is impossible to prevent anyone from sending Ether e.g. with selfdestruct. */
return price;
}
/**
* @dev Return whether or not two orders can be matched with each other by basic parameters (does not check order signatures / calldata or perform static calls)
* @param buy Buy-side order
* @param sell Sell-side order
* @return Whether or not the two orders can be matched
*/
function ordersCanMatch(Order memory buy, Order memory sell)
internal
view
returns (bool)
{
return (
/* Must be opposite-side. */
(buy.side == SaleKindInterface.Side.Buy && sell.side == SaleKindInterface.Side.Sell) &&
/* Must use same fee method. */
(buy.feeMethod == sell.feeMethod) &&
/* Must use same payment token. */
(buy.paymentToken == sell.paymentToken) &&
/* Must match maker/taker addresses. */
(sell.taker == address(0) || sell.taker == buy.maker) &&
(buy.taker == address(0) || buy.taker == sell.maker) &&
/* One must be maker and the other must be taker (no bool XOR in Solidity). */
((sell.feeRecipient == address(0) && buy.feeRecipient != address(0)) || (sell.feeRecipient != address(0) && buy.feeRecipient == address(0))) &&
/* Must match target. */
(buy.target == sell.target) &&
/* Must match howToCall. */
(buy.howToCall == sell.howToCall) &&
/* Buy-side order must be settleable. */
SaleKindInterface.canSettleOrder(buy.listingTime, buy.expirationTime) &&
/* Sell-side order must be settleable. */
SaleKindInterface.canSettleOrder(sell.listingTime, sell.expirationTime)
);
}
/**
* @dev Atomically match two orders, ensuring validity of the match, and execute all associated state transitions. Protected against reentrancy by a contract-global lock.
* @param buy Buy-side order
* @param buySig Buy-side order signature
* @param sell Sell-side order
* @param sellSig Sell-side order signature
*/
function atomicMatch(Order memory buy, Sig memory buySig, Order memory sell, Sig memory sellSig, bytes32 metadata)
internal
reentrancyGuard
{
/* CHECKS */
/* Ensure buy order validity and calculate hash if necessary. */
bytes32 buyHash;
if (buy.maker == msg.sender) {
require(validateOrderParameters(buy));
} else {
buyHash = requireValidOrder(buy, buySig);
}
/* Ensure sell order validity and calculate hash if necessary. */
bytes32 sellHash;
if (sell.maker == msg.sender) {
require(validateOrderParameters(sell));
} else {
sellHash = requireValidOrder(sell, sellSig);
}
/* Must be matchable. */
require(ordersCanMatch(buy, sell));
/* Target must exist (prevent malicious selfdestructs just prior to order settlement). */
uint size;
address target = sell.target;
assembly {
size := extcodesize(target)
}
require(size > 0);
/* Must match calldata after replacement, if specified. */
if (buy.replacementPattern.length > 0) {
ArrayUtils.guardedArrayReplace(buy.calldata, sell.calldata, buy.replacementPattern);
}
if (sell.replacementPattern.length > 0) {
ArrayUtils.guardedArrayReplace(sell.calldata, buy.calldata, sell.replacementPattern);
}
require(ArrayUtils.arrayEq(buy.calldata, sell.calldata));
/* Retrieve delegateProxy contract. */
OwnableDelegateProxy delegateProxy = registry.proxies(sell.maker);
/* Proxy must exist. */
require(delegateProxy != address(0));
/* Assert implementation. */
require(delegateProxy.implementation() == registry.delegateProxyImplementation());
/* Access the passthrough AuthenticatedProxy. */
AuthenticatedProxy proxy = AuthenticatedProxy(delegateProxy);
/* EFFECTS */
/* Mark previously signed or approved orders as finalized. */
if (msg.sender != buy.maker) {
cancelledOrFinalized[buyHash] = true;
}
if (msg.sender != sell.maker) {
cancelledOrFinalized[sellHash] = true;
}
/* INTERACTIONS */
/* Execute funds transfer and pay fees. */
uint price = executeFundsTransfer(buy, sell);
/* Execute specified call through proxy. */
require(proxy.proxy(sell.target, sell.howToCall, sell.calldata));
/* Static calls are intentionally done after the effectful call so they can check resulting state. */
/* Handle buy-side static call if specified. */
if (buy.staticTarget != address(0)) {
require(staticCall(buy.staticTarget, sell.calldata, buy.staticExtradata));
}
/* Handle sell-side static call if specified. */
if (sell.staticTarget != address(0)) {
require(staticCall(sell.staticTarget, sell.calldata, sell.staticExtradata));
}
/* Log match event. */
emit OrdersMatched(buyHash, sellHash, sell.feeRecipient != address(0) ? sell.maker : buy.maker, sell.feeRecipient != address(0) ? buy.maker : sell.maker, price, metadata);
}
}