-
Notifications
You must be signed in to change notification settings - Fork 15
/
Copy pathOrderCombiner.sol
793 lines (703 loc) · 35.9 KB
/
OrderCombiner.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
// SPDX-License-Identifier: MIT
pragma solidity 0.8.13;
import { Side, ItemType } from "./ConsiderationEnums.sol";
// prettier-ignore
import {
AdditionalRecipient,
OfferItem,
ConsiderationItem,
SpentItem,
ReceivedItem,
OrderParameters,
Fulfillment,
FulfillmentComponent,
Execution,
Order,
AdvancedOrder,
CriteriaResolver
} from "./ConsiderationStructs.sol";
import { OrderFulfiller } from "./OrderFulfiller.sol";
import { FulfillmentApplier } from "./FulfillmentApplier.sol";
import "./ConsiderationConstants.sol";
/**
* @title OrderCombiner
* @author 0age
* @notice OrderCombiner contains logic for fulfilling combinations of orders,
* either by matching offer items to consideration items or by
* fulfilling orders where available.
*/
contract OrderCombiner is OrderFulfiller, FulfillmentApplier {
/**
* @dev Derive and set hashes, reference chainId, and associated domain
* separator during deployment.
*
* @param conduitController A contract that deploys conduits, or proxies
* that may optionally be used to transfer approved
* ERC20/721/1155 tokens.
*/
constructor(address conduitController) OrderFulfiller(conduitController) {}
/**
* @notice Internal function to attempt to fill a group of orders, fully or
* partially, with an arbitrary number of items for offer and
* consideration per order alongside criteria resolvers containing
* specific token identifiers and associated proofs. Any order that
* is not currently active, has already been fully filled, or has
* been cancelled will be omitted. Remaining offer and consideration
* items will then be aggregated where possible as indicated by the
* supplied offer and consideration component arrays and aggregated
* items will be transferred to the fulfiller or to each intended
* recipient, respectively. Note that a failing item transfer or an
* issue with order formatting will cause the entire batch to fail.
*
* @param advancedOrders The orders to fulfill along with the
* fraction of those orders to attempt to
* fill. Note that both the offerer and the
* fulfiller must first approve this
* contract (or a conduit if indicated by
* the order) to transfer any relevant
* tokens on their behalf and that
* contracts must implement
* `onERC1155Received` in order to receive
* ERC1155 tokens as consideration. Also
* note that all offer and consideration
* components must have no remainder after
* multiplication of the respective amount
* with the supplied fraction for an
* order's partial fill amount to be
* considered valid.
* @param criteriaResolvers An array where each element contains a
* reference to a specific offer or
* consideration, a token identifier, and a
* proof that the supplied token identifier
* is contained in the merkle root held by
* the item in question's criteria element.
* Note that an empty criteria indicates
* that any (transferrable) token
* identifier on the token in question is
* valid and that no associated proof needs
* to be supplied.
* @param offerFulfillments An array of FulfillmentComponent arrays
* indicating which offer items to attempt
* to aggregate when preparing executions.
* @param considerationFulfillments An array of FulfillmentComponent arrays
* indicating which consideration items to
* attempt to aggregate when preparing
* executions.
* @param fulfillerConduitKey A bytes32 value indicating what conduit,
* if any, to source the fulfiller's token
* approvals from. The zero hash signifies
* that no conduit should be used (and
* direct approvals set on Consideration).
* @param maximumFulfilled The maximum number of orders to fulfill.
*
* @return availableOrders An array of booleans indicating if each order
* with an index corresponding to the index of the
* returned boolean was fulfillable or not.
* @return executions An array of elements indicating the sequence of
* transfers performed as part of matching the given
* orders.
*/
function _fulfillAvailableAdvancedOrders(
AdvancedOrder[] memory advancedOrders,
CriteriaResolver[] memory criteriaResolvers,
FulfillmentComponent[][] calldata offerFulfillments,
FulfillmentComponent[][] calldata considerationFulfillments,
bytes32 fulfillerConduitKey,
uint256 maximumFulfilled
)
internal
returns (bool[] memory availableOrders, Execution[] memory executions)
{
// Validate orders, apply amounts, & determine if they utilize conduits.
_validateOrdersAndPrepareToFulfill(
advancedOrders,
criteriaResolvers,
false, // Signifies that invalid orders should NOT revert.
maximumFulfilled
);
// Aggregate used offer and consideration items and execute transfers.
(availableOrders, executions) = _executeAvailableFulfillments(
advancedOrders,
offerFulfillments,
considerationFulfillments,
fulfillerConduitKey
);
// Return order fulfillment details and executions.
return (availableOrders, executions);
}
/**
* @dev Internal function to validate a group of orders, update their
* statuses, reduce amounts by their previously filled fractions, apply
* criteria resolvers, and emit OrderFulfilled events.
*
* @param advancedOrders The advanced orders to validate and reduce by
* their previously filled amounts.
* @param criteriaResolvers An array where each element contains a reference
* to a specific order as well as that order's
* offer or consideration, a token identifier, and
* a proof that the supplied token identifier is
* contained in the order's merkle root. Note that
* a root of zero indicates that any transferrable
* token identifier is valid and that no proof
* needs to be supplied.
* @param revertOnInvalid A boolean indicating whether to revert on any
* order being invalid; setting this to false will
* instead cause the invalid order to be skipped.
* @param maximumFulfilled The maximum number of orders to fulfill.
*/
function _validateOrdersAndPrepareToFulfill(
AdvancedOrder[] memory advancedOrders,
CriteriaResolver[] memory criteriaResolvers,
bool revertOnInvalid,
uint256 maximumFulfilled
) internal {
// Ensure this function cannot be triggered during a reentrant call.
_setReentrancyGuard();
// Read length of orders array and place on the stack.
uint256 totalOrders = advancedOrders.length;
// Track the order hash for each order being fulfilled.
bytes32[] memory orderHashes = new bytes32[](totalOrders);
// Override orderHashes length to zero after memory has been allocated.
assembly {
mstore(orderHashes, 0)
}
// Skip overflow checks as all for loops are indexed starting at zero.
unchecked {
// Iterate over each order.
for (uint256 i = 0; i < totalOrders; ++i) {
// Retrieve the current order.
AdvancedOrder memory advancedOrder = advancedOrders[i];
// Determine if max number orders have already been fulfilled.
if (maximumFulfilled == 0) {
// Mark fill fraction as zero as the order will not be used.
advancedOrder.numerator = 0;
// Update the length of the orderHashes array.
assembly {
mstore(orderHashes, add(i, 1))
}
// Continue iterating through the remaining orders.
continue;
}
// Validate it, update status, and determine fraction to fill.
(
bytes32 orderHash,
uint256 numerator,
uint256 denominator
) = _validateOrderAndUpdateStatus(
advancedOrder,
criteriaResolvers,
revertOnInvalid,
orderHashes
);
// Update the length of the orderHashes array.
assembly {
mstore(orderHashes, add(i, 1))
}
// Do not track hash or adjust prices if order is not fulfilled.
if (numerator == 0) {
// Mark fill fraction as zero if the order is not fulfilled.
advancedOrder.numerator = 0;
// Continue iterating through the remaining orders.
continue;
}
// Otherwise, track the order hash in question.
orderHashes[i] = orderHash;
// Decrement the number of fulfilled orders.
maximumFulfilled--;
// Place the start time for the order on the stack.
uint256 startTime = advancedOrder.parameters.startTime;
// Derive the duration for the order and place it on the stack.
uint256 duration = advancedOrder.parameters.endTime - startTime;
// Derive time elapsed since the order started & place on stack.
uint256 elapsed = block.timestamp - startTime;
// Derive time remaining until order expires and place on stack.
uint256 remaining = duration - elapsed;
// Retrieve array of offer items for the order in question.
OfferItem[] memory offer = advancedOrder.parameters.offer;
// Iterate over each offer item on the order.
for (uint256 j = 0; j < offer.length; ++j) {
// Retrieve the offer item.
OfferItem memory offerItem = offer[j];
// Apply order fill fraction to offer item end amount.
uint256 endAmount = _getFraction(
numerator,
denominator,
offerItem.endAmount
);
// Reuse same fraction if start and end amounts are equal.
if (offerItem.startAmount == offerItem.endAmount) {
// Apply derived amount to both start and end amount.
offerItem.startAmount = endAmount;
} else {
// Apply order fill fraction to offer item start amount.
offerItem.startAmount = _getFraction(
numerator,
denominator,
offerItem.startAmount
);
}
// Update end amount in memory to match the derived amount.
offerItem.endAmount = endAmount;
// Adjust offer amount using current time; round down.
offerItem.startAmount = _locateCurrentAmount(
offerItem.startAmount,
offerItem.endAmount,
elapsed,
remaining,
duration,
false // round down
);
}
// Retrieve array of consideration items for order in question.
ConsiderationItem[] memory consideration = (
advancedOrder.parameters.consideration
);
// Iterate over each consideration item on the order.
for (uint256 j = 0; j < consideration.length; ++j) {
// Retrieve the consideration item.
ConsiderationItem memory considerationItem = (
consideration[j]
);
// Apply fraction to consideration item end amount.
uint256 endAmount = _getFraction(
numerator,
denominator,
considerationItem.endAmount
);
// Reuse same fraction if start and end amounts are equal.
if (
considerationItem.startAmount ==
considerationItem.endAmount
) {
// Apply derived amount to both start and end amount.
considerationItem.startAmount = endAmount;
} else {
// Apply fraction to consideration item start amount.
considerationItem.startAmount = _getFraction(
numerator,
denominator,
considerationItem.startAmount
);
}
// Update end amount in memory to match the derived amount.
considerationItem.endAmount = endAmount;
// Adjust consideration amount using current time; round up.
considerationItem.startAmount = (
_locateCurrentAmount(
considerationItem.startAmount,
considerationItem.endAmount,
elapsed,
remaining,
duration,
true // round up
)
);
// Utilize assembly to manually "shift" the recipient value.
assembly {
// Write recipient to endAmount, as endAmount is not
// used from this point on and can be repurposed to fit
// the layout of a ReceivedItem.
mstore(
add(
considerationItem,
ReceivedItem_recipient_offset // old endAmount
),
mload(
add(
considerationItem,
ConsiderationItem_recipient_offset
)
)
)
}
}
}
}
// Apply criteria resolvers to each order as applicable.
_applyCriteriaResolvers(advancedOrders, criteriaResolvers);
// Determine the fulfiller (revertOnInvalid ? address(0) : msg.sender).
address fulfiller;
// Utilize assembly to operate on revertOnInvalid boolean as an integer.
assembly {
// Set the fulfiller to the caller if revertOnValid is false.
fulfiller := mul(iszero(revertOnInvalid), caller())
}
// Emit an event for each order signifying that it has been fulfilled.
// Skip overflow checks as all for loops are indexed starting at zero.
unchecked {
// Iterate over each order.
for (uint256 i = 0; i < totalOrders; ++i) {
// Do not emit an event if no order hash is present.
if (orderHashes[i] == bytes32(0)) {
continue;
}
// Retrieve parameters for the order in question.
OrderParameters memory orderParameters = (
advancedOrders[i].parameters
);
// Emit an OrderFulfilled event.
_emitOrderFulfilledEvent(
orderHashes[i],
orderParameters.offerer,
orderParameters.zone,
fulfiller,
orderParameters.offer,
orderParameters.consideration
);
}
}
}
/**
* @dev Internal function to fulfill a group of validated orders, fully or
* partially, with an arbitrary number of items for offer and
* consideration per order and to execute transfers. Any order that is
* not currently active, has already been fully filled, or has been
* cancelled will be omitted. Remaining offer and consideration items
* will then be aggregated where possible as indicated by the supplied
* offer and consideration component arrays and aggregated items will
* be transferred to the fulfiller or to each intended recipient,
* respectively. Note that a failing item transfer or an issue with
* order formatting will cause the entire batch to fail.
*
* @param advancedOrders The orders to fulfill along with the
* fraction of those orders to attempt to
* fill. Note that both the offerer and the
* fulfiller must first approve this
* contract (or the conduit if indicated by
* the order) to transfer any relevant
* tokens on their behalf and that
* contracts must implement
* `onERC1155Received` in order to receive
* ERC1155 tokens as consideration. Also
* note that all offer and consideration
* components must have no remainder after
* multiplication of the respective amount
* with the supplied fraction for an
* order's partial fill amount to be
* considered valid.
* @param offerFulfillments An array of FulfillmentComponent arrays
* indicating which offer items to attempt
* to aggregate when preparing executions.
* @param considerationFulfillments An array of FulfillmentComponent arrays
* indicating which consideration items to
* attempt to aggregate when preparing
* executions.
* @param fulfillerConduitKey A bytes32 value indicating what conduit,
* if any, to source the fulfiller's token
* approvals from. The zero hash signifies
* that no conduit should be used, with
* direct approvals set on Consideration.
*
* @return availableOrders An array of booleans indicating if each order
* with an index corresponding to the index of the
* returned boolean was fulfillable or not.
* @return executions An array of elements indicating the sequence of
* transfers performed as part of matching the given
* orders.
*/
function _executeAvailableFulfillments(
AdvancedOrder[] memory advancedOrders,
FulfillmentComponent[][] memory offerFulfillments,
FulfillmentComponent[][] memory considerationFulfillments,
bytes32 fulfillerConduitKey
)
internal
returns (bool[] memory availableOrders, Execution[] memory executions)
{
// Retrieve length of offer fulfillments array and place on the stack.
uint256 totalOfferFulfillments = offerFulfillments.length;
// Retrieve length of consideration fulfillments array & place on stack.
uint256 totalConsiderationFulfillments = (
considerationFulfillments.length
);
// Allocate an execution for each offer and consideration fulfillment.
executions = new Execution[](
totalOfferFulfillments + totalConsiderationFulfillments
);
// Skip overflow checks as all for loops are indexed starting at zero.
unchecked {
// Track number of filtered executions.
uint256 totalFilteredExecutions = 0;
// Iterate over each offer fulfillment.
for (uint256 i = 0; i < totalOfferFulfillments; ++i) {
/// Retrieve the offer fulfillment components in question.
FulfillmentComponent[] memory components = (
offerFulfillments[i]
);
// Derive aggregated execution corresponding with fulfillment.
Execution memory execution = _aggregateAvailable(
advancedOrders,
Side.OFFER,
components,
fulfillerConduitKey
);
// If offerer and recipient on the execution are the same...
if (execution.item.recipient == execution.offerer) {
// increment total filtered executions.
totalFilteredExecutions += 1;
} else {
// Otherwise, assign the execution to the executions array.
executions[i - totalFilteredExecutions] = execution;
}
}
// Iterate over each consideration fulfillment.
for (uint256 i = 0; i < totalConsiderationFulfillments; ++i) {
/// Retrieve consideration fulfillment components in question.
FulfillmentComponent[] memory components = (
considerationFulfillments[i]
);
// Derive aggregated execution corresponding with fulfillment.
Execution memory execution = _aggregateAvailable(
advancedOrders,
Side.CONSIDERATION,
components,
fulfillerConduitKey
);
// If offerer and recipient on the execution are the same...
if (execution.item.recipient == execution.offerer) {
// increment total filtered executions.
totalFilteredExecutions += 1;
} else {
// Otherwise, assign the execution to the executions array.
executions[
i + totalOfferFulfillments - totalFilteredExecutions
] = execution;
}
}
// If some number of executions have been filtered...
if (totalFilteredExecutions != 0) {
// reduce the total length of the executions array.
assembly {
mstore(
executions,
sub(mload(executions), totalFilteredExecutions)
)
}
}
}
// Revert if no orders are available.
if (executions.length == 0) {
revert NoSpecifiedOrdersAvailable();
}
// Perform final checks and return.
availableOrders = _performFinalChecksAndExecuteOrders(
advancedOrders,
executions
);
return (availableOrders, executions);
}
/**
* @dev Internal function to perform a final check that each consideration
* item for an arbitrary number of fulfilled orders has been met and to
* trigger associated executions, transferring the respective items.
*
* @param advancedOrders The orders to check and perform executions for.
* @param executions An array of elements indicating the sequence of
* transfers to perform when fulfilling the given
* orders.
*
* @return availableOrders An array of booleans indicating if each order
* with an index corresponding to the index of the
* returned boolean was fulfillable or not.
*/
function _performFinalChecksAndExecuteOrders(
AdvancedOrder[] memory advancedOrders,
Execution[] memory executions
) internal returns (bool[] memory availableOrders) {
// Retrieve the length of the advanced orders array and place on stack.
uint256 totalOrders = advancedOrders.length;
// Initialize array for tracking available orders.
availableOrders = new bool[](totalOrders);
// Skip overflow checks as all for loops are indexed starting at zero.
unchecked {
// Iterate over orders to ensure all considerations are met.
for (uint256 i = 0; i < totalOrders; ++i) {
// Retrieve the order in question.
AdvancedOrder memory advancedOrder = advancedOrders[i];
// Skip consideration item checks for order if not fulfilled.
if (advancedOrder.numerator == 0) {
// Note: orders do not need to be marked as unavailable as a
// new memory region has been allocated. Review carefully if
// altering compiler version or managing memory manually.
continue;
}
// Mark the order as available.
availableOrders[i] = true;
// Retrieve consideration items to ensure they are fulfilled.
ConsiderationItem[] memory consideration = (
advancedOrder.parameters.consideration
);
// Iterate over each consideration item to ensure it is met.
for (uint256 j = 0; j < consideration.length; ++j) {
// Retrieve remaining amount on the consideration item.
uint256 unmetAmount = consideration[j].startAmount;
// Revert if the remaining amount is not zero.
if (unmetAmount != 0) {
revert ConsiderationNotMet(i, j, unmetAmount);
}
}
}
}
// Put ether value supplied by the caller on the stack.
uint256 etherRemaining = msg.value;
// Initialize an accumulator array. From this point forward, no new
// memory regions can be safely allocated until the accumulator is no
// longer being utilized, as the accumulator operates in an open-ended
// fashion from this memory pointer; existing memory may still be
// accessed and modified, however.
bytes memory accumulator = new bytes(AccumulatorDisarmed);
// Iterate over each execution.
for (uint256 i = 0; i < executions.length; ) {
// Retrieve the execution and the associated received item.
Execution memory execution = executions[i];
ReceivedItem memory item = execution.item;
// If execution transfers native tokens, reduce value available.
if (item.itemType == ItemType.NATIVE) {
// Ensure that sufficient native tokens are still available.
if (item.amount > etherRemaining) {
revert InsufficientEtherSupplied();
}
// Skip underflow check as amount is less than ether remaining.
unchecked {
etherRemaining -= item.amount;
}
}
// Transfer the item specified by the execution.
_transfer(
item,
execution.offerer,
execution.conduitKey,
accumulator
);
// Skip overflow check as for loop is indexed starting at zero.
unchecked {
++i;
}
}
// Trigger any remaining accumulated transfers via call to the conduit.
_triggerIfArmed(accumulator);
// If any ether remains after fulfillments, return it to the caller.
if (etherRemaining != 0) {
_transferEth(payable(msg.sender), etherRemaining);
}
// Clear the reentrancy guard.
_clearReentrancyGuard();
// Return the array containing available orders.
return (availableOrders);
}
/**
* @dev Internal function to match an arbitrary number of full or partial
* orders, each with an arbitrary number of items for offer and
* consideration, supplying criteria resolvers containing specific
* token identifiers and associated proofs as well as fulfillments
* allocating offer components to consideration components.
*
* @param advancedOrders The advanced orders to match. Note that both the
* offerer and fulfiller on each order must first
* approve this contract (or their conduit if
* indicated by the order) to transfer any relevant
* tokens on their behalf and each consideration
* recipient must implement `onERC1155Received` in
* order to receive ERC1155 tokens. Also note that
* the offer and consideration components for each
* order must have no remainder after multiplying
* the respective amount with the supplied fraction
* in order for the group of partial fills to be
* considered valid.
* @param criteriaResolvers An array where each element contains a reference
* to a specific order as well as that order's
* offer or consideration, a token identifier, and
* a proof that the supplied token identifier is
* contained in the order's merkle root. Note that
* an empty root indicates that any (transferrable)
* token identifier is valid and that no associated
* proof needs to be supplied.
* @param fulfillments An array of elements allocating offer components
* to consideration components. Note that each
* consideration component must be fully met in
* order for the match operation to be valid.
*
* @return executions An array of elements indicating the sequence of
* transfers performed as part of matching the given
* orders.
*/
function _matchAdvancedOrders(
AdvancedOrder[] memory advancedOrders,
CriteriaResolver[] memory criteriaResolvers,
Fulfillment[] calldata fulfillments
) internal returns (Execution[] memory executions) {
// Validate orders, update order status, and determine item amounts.
_validateOrdersAndPrepareToFulfill(
advancedOrders,
criteriaResolvers,
true, // Signifies that invalid orders should revert.
advancedOrders.length
);
// Fulfill the orders using the supplied fulfillments.
return _fulfillAdvancedOrders(advancedOrders, fulfillments);
}
/**
* @dev Internal function to fulfill an arbitrary number of orders, either
* full or partial, after validating, adjusting amounts, and applying
* criteria resolvers.
*
* @param advancedOrders The orders to match, including a fraction to
* attempt to fill for each order.
* @param fulfillments An array of elements allocating offer
* components to consideration components. Note
* that the final amount of each consideration
* component must be zero for a match operation to
* be considered valid.
*
* @return executions An array of elements indicating the sequence of
* transfers performed as part of matching the given
* orders.
*/
function _fulfillAdvancedOrders(
AdvancedOrder[] memory advancedOrders,
Fulfillment[] calldata fulfillments
) internal returns (Execution[] memory executions) {
// Retrieve fulfillments array length and place on the stack.
uint256 totalFulfillments = fulfillments.length;
// Allocate executions by fulfillment and apply them to each execution.
executions = new Execution[](totalFulfillments);
// Skip overflow checks as all for loops are indexed starting at zero.
unchecked {
// Track number of filtered executions.
uint256 totalFilteredExecutions = 0;
// Iterate over each fulfillment.
for (uint256 i = 0; i < totalFulfillments; ++i) {
/// Retrieve the fulfillment in question.
Fulfillment calldata fulfillment = fulfillments[i];
// Derive the execution corresponding with the fulfillment.
Execution memory execution = _applyFulfillment(
advancedOrders,
fulfillment.offerComponents,
fulfillment.considerationComponents
);
// If offerer and recipient on the execution are the same...
if (execution.item.recipient == execution.offerer) {
// increment total filtered executions.
totalFilteredExecutions += 1;
} else {
// Otherwise, assign the execution to the executions array.
executions[i - totalFilteredExecutions] = execution;
}
}
// If some number of executions have been filtered...
if (totalFilteredExecutions != 0) {
// reduce the total length of the executions array.
assembly {
mstore(
executions,
sub(mload(executions), totalFilteredExecutions)
)
}
}
}
// Perform final checks and execute orders.
_performFinalChecksAndExecuteOrders(advancedOrders, executions);
// Return the executions array.
return (executions);
}
}