-
Notifications
You must be signed in to change notification settings - Fork 15
/
Copy pathFulfillmentApplier.sol
760 lines (649 loc) · 29.9 KB
/
FulfillmentApplier.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
// SPDX-License-Identifier: MIT
pragma solidity 0.8.13;
import { ItemType, Side } from "./ConsiderationEnums.sol";
// prettier-ignore
import {
OfferItem,
ConsiderationItem,
ReceivedItem,
OrderParameters,
AdvancedOrder,
Execution,
FulfillmentComponent
} from "./ConsiderationStructs.sol";
import "./ConsiderationConstants.sol";
// prettier-ignore
import {
FulfillmentApplicationErrors
} from "../interfaces/FulfillmentApplicationErrors.sol";
/**
* @title FulfillmentApplier
* @author 0age
* @notice FulfillmentApplier contains logic related to applying fulfillments,
* both as part of order matching (where offer items are matched to
* consideration items) as well as fulfilling available orders (where
* order items and consideration items are independently aggregated).
*/
contract FulfillmentApplier is FulfillmentApplicationErrors {
/**
* @dev Internal view function to match offer items to consideration items
* on a group of orders via a supplied fulfillment.
*
* @param advancedOrders The orders to match.
* @param offerComponents An array designating offer components to
* match to consideration components.
* @param considerationComponents An array designating consideration
* components to match to offer components.
* Note that each consideration amount must
* be zero in order for the match operation
* to be valid.
*
* @return execution The transfer performed as a result of the fulfillment.
*/
function _applyFulfillment(
AdvancedOrder[] memory advancedOrders,
FulfillmentComponent[] calldata offerComponents,
FulfillmentComponent[] calldata considerationComponents
) internal view returns (Execution memory execution) {
// Ensure 1+ of both offer and consideration components are supplied.
if (
offerComponents.length == 0 || considerationComponents.length == 0
) {
revert OfferAndConsiderationRequiredOnFulfillment();
}
// Declare a new Execution struct.
Execution memory considerationExecution;
// Validate & aggregate consideration items to new Execution object.
_aggregateValidFulfillmentConsiderationItems(
advancedOrders,
considerationComponents,
considerationExecution
);
// Retrieve the consideration item from the execution struct.
ReceivedItem memory considerationItem = considerationExecution.item;
// Validate & aggregate offer items to Execution object.
_aggregateValidFulfillmentOfferItems(
advancedOrders,
offerComponents,
execution
);
// Ensure offer and consideration share types, tokens and identifiers.
if (
execution.item.itemType != considerationItem.itemType ||
execution.item.token != considerationItem.token ||
execution.item.identifier != considerationItem.identifier
) {
revert MismatchedFulfillmentOfferAndConsiderationComponents();
}
// If total consideration amount exceeds the offer amount...
if (considerationItem.amount > execution.item.amount) {
// Retrieve the first consideration component from the fulfillment.
FulfillmentComponent memory targetComponent = (
considerationComponents[0]
);
// Add excess consideration item amount to original array of orders.
advancedOrders[targetComponent.orderIndex]
.parameters
.consideration[targetComponent.itemIndex]
.startAmount = considerationItem.amount - execution.item.amount;
// Reduce total consideration amount to equal the offer amount.
considerationItem.amount = execution.item.amount;
} else {
// Retrieve the first offer component from the fulfillment.
FulfillmentComponent memory targetComponent = (offerComponents[0]);
// Add excess offer item amount to the original array of orders.
advancedOrders[targetComponent.orderIndex]
.parameters
.offer[targetComponent.itemIndex]
.startAmount = execution.item.amount - considerationItem.amount;
}
// Reuse execution struct with consideration amount and recipient.
execution.item.amount = considerationItem.amount;
execution.item.recipient = considerationItem.recipient;
// Return the final execution that will be triggered for relevant items.
return execution; // Execution(considerationItem, offerer, conduitKey);
}
/**
* @dev Internal view function to aggregate offer or consideration items
* from a group of orders into a single execution via a supplied array
* of fulfillment components. Items that are not available to aggregate
* will not be included in the aggregated execution.
*
* @param advancedOrders The orders to aggregate.
* @param side The side (i.e. offer or consideration).
* @param fulfillmentComponents An array designating item components to
* aggregate if part of an available order.
* @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 approvals
* set directly on this contract.
*
* @return execution The transfer performed as a result of the fulfillment.
*/
function _aggregateAvailable(
AdvancedOrder[] memory advancedOrders,
Side side,
FulfillmentComponent[] memory fulfillmentComponents,
bytes32 fulfillerConduitKey
) internal view returns (Execution memory execution) {
// Skip overflow / underflow checks; conditions checked or unreachable.
unchecked {
// Retrieve fulfillment components array length and place on stack.
// Ensure at least one fulfillment component has been supplied.
if (fulfillmentComponents.length == 0) {
revert MissingFulfillmentComponentOnAggregation(side);
}
// If the fulfillment components are offer components...
if (side == Side.OFFER) {
// Return execution for aggregated items provided by offerer.
_aggregateValidFulfillmentOfferItems(
advancedOrders,
fulfillmentComponents,
execution
);
} else {
// Otherwise, fulfillment components are consideration
// components. Return execution for aggregated items provided by
// the fulfiller.
_aggregateValidFulfillmentConsiderationItems(
advancedOrders,
fulfillmentComponents,
execution
);
// Set the caller as the offerer on the execution.
execution.offerer = msg.sender;
// Set fulfiller conduit key as the conduit key on execution.
execution.conduitKey = fulfillerConduitKey;
}
// Set the offerer as the receipient if execution amount is nonzero.
if (execution.item.amount == 0) {
execution.item.recipient = payable(execution.offerer);
}
}
}
/**
* @dev Internal pure function to aggregate a group of offer items using
* supplied directives on which component items are candidates for
* aggregation, skipping items on orders that are not available.
*
* @param advancedOrders The orders to aggregate offer items from.
* @param offerComponents An array of FulfillmentComponent structs
* indicating the order index and item index of each
* candidate offer item for aggregation.
* @param execution The execution to apply the aggregation to.
*/
function _aggregateValidFulfillmentOfferItems(
AdvancedOrder[] memory advancedOrders,
FulfillmentComponent[] memory offerComponents,
Execution memory execution
) internal view {
assembly {
// Declare function for reverts on invalid fulfillment data.
function throwInvalidFulfillmentComponentData() {
// Store the InvalidFulfillmentComponentData error signature.
mstore(0, InvalidFulfillmentComponentData_error_signature)
// Return, supplying InvalidFulfillmentComponentData signature.
revert(0, InvalidFulfillmentComponentData_error_len)
}
// Declare function for reverts due to arithmetic overflows.
function throwOverflow() {
// Store the Panic error signature.
mstore(0, Panic_error_signature)
// Store the arithmetic (0x11) panic code as initial argument.
mstore(Panic_error_offset, Panic_arithmetic)
// Return, supplying Panic signature and arithmetic code.
revert(0, Panic_error_length)
}
// Get position in offerComponents head.
let fulfillmentHeadPtr := add(offerComponents, OneWord)
// Retrieve the order index using the fulfillment pointer.
let orderIndex := mload(mload(fulfillmentHeadPtr))
// Ensure that the order index is not out of range.
if iszero(lt(orderIndex, mload(advancedOrders))) {
throwInvalidFulfillmentComponentData()
}
// Read advancedOrders[orderIndex] pointer from its array head.
let orderPtr := mload(
// Calculate head position of advancedOrders[orderIndex].
add(add(advancedOrders, OneWord), mul(orderIndex, OneWord))
)
// Read the pointer to OrderParameters from the AdvancedOrder.
let paramsPtr := mload(orderPtr)
// Load the offer array pointer.
let offerArrPtr := mload(
add(paramsPtr, OrderParameters_offer_head_offset)
)
// Retrieve item index using an offset of the fulfillment pointer.
let itemIndex := mload(
add(mload(fulfillmentHeadPtr), Fulfillment_itemIndex_offset)
)
// Only continue if the fulfillment is not invalid.
if iszero(lt(itemIndex, mload(offerArrPtr))) {
throwInvalidFulfillmentComponentData()
}
// Retrieve consideration item pointer using the item index.
let offerItemPtr := mload(
add(
// Get pointer to beginning of receivedItem.
add(offerArrPtr, OneWord),
// Calculate offset to pointer for desired order.
mul(itemIndex, OneWord)
)
)
// Declare a variable for the final aggregated item amount.
let amount := 0
// Create variable to track errors encountered with amount.
let errorBuffer := 0
// Only add offer amount to execution amount on a nonzero numerator.
if mload(add(orderPtr, AdvancedOrder_numerator_offset)) {
// Retrieve amount pointer using consideration item pointer.
let amountPtr := add(offerItemPtr, Common_amount_offset)
// Set the amount.
amount := mload(amountPtr)
// Zero out amount on item to indicate it is credited.
mstore(amountPtr, 0)
// Buffer indicating whether issues were found.
errorBuffer := iszero(amount)
}
// Retrieve the received item pointer.
let receivedItemPtr := mload(execution)
// Set the caller as the recipient on the received item.
mstore(
add(receivedItemPtr, ReceivedItem_recipient_offset),
caller()
)
// Set the item type on the received item.
mstore(receivedItemPtr, mload(offerItemPtr))
// Set the token on the received item.
mstore(
add(receivedItemPtr, Common_token_offset),
mload(add(offerItemPtr, Common_token_offset))
)
// Set the identifier on the received item.
mstore(
add(receivedItemPtr, Common_identifier_offset),
mload(add(offerItemPtr, Common_identifier_offset))
)
// Set the offerer on returned execution using order pointer.
mstore(add(execution, Execution_offerer_offset), mload(paramsPtr))
// Set conduitKey on returned execution via offset of order pointer.
mstore(
add(execution, Execution_conduit_offset),
mload(add(paramsPtr, OrderParameters_conduit_offset))
)
// Calculate the hash of (itemType, token, identifier).
let dataHash := keccak256(
receivedItemPtr,
ReceivedItem_CommonParams_size
)
// Get position one word past last element in head of array.
let endPtr := add(
offerComponents,
mul(mload(offerComponents), OneWord)
)
// Iterate over remaining offer components.
// prettier-ignore
for {} lt(fulfillmentHeadPtr, endPtr) {} {
// Increment the pointer to the fulfillment head by one word.
fulfillmentHeadPtr := add(fulfillmentHeadPtr, OneWord)
// Get the order index using the fulfillment pointer.
orderIndex := mload(mload(fulfillmentHeadPtr))
// Ensure the order index is in range.
if iszero(lt(orderIndex, mload(advancedOrders))) {
throwInvalidFulfillmentComponentData()
}
// Get pointer to AdvancedOrder element.
orderPtr := mload(
add(
add(advancedOrders, OneWord),
mul(orderIndex, OneWord)
)
)
// Only continue if numerator is not zero.
if iszero(mload(
add(orderPtr, AdvancedOrder_numerator_offset)
)) {
continue
}
// Read the pointer to OrderParameters from the AdvancedOrder.
paramsPtr := mload(orderPtr)
// Load offer array pointer.
offerArrPtr := mload(
add(
paramsPtr,
OrderParameters_offer_head_offset
)
)
// Get the item index using the fulfillment pointer.
itemIndex := mload(add(mload(fulfillmentHeadPtr), OneWord))
// Throw if itemIndex is out of the range of array.
if iszero(
lt(itemIndex, mload(offerArrPtr))
) {
throwInvalidFulfillmentComponentData()
}
// Retrieve offer item pointer using index.
offerItemPtr := mload(
add(
// Get pointer to beginning of receivedItem.
add(offerArrPtr, OneWord),
// Use offset to pointer for desired order.
mul(itemIndex, OneWord)
)
)
// Retrieve amount pointer using offer item pointer.
let amountPtr := add(
offerItemPtr,
Common_amount_offset
)
// Add offer amount to execution amount.
let newAmount := add(amount, mload(amountPtr))
// Update error buffer (1 = zero amount, 2 = overflow).
errorBuffer := or(
errorBuffer,
or(
shl(1, lt(newAmount, amount)),
iszero(mload(amountPtr))
)
)
// Update the amount to the new, summed amount.
amount := newAmount
// Zero out amount on original item to indicate it is credited.
mstore(amountPtr, 0)
// Ensure the indicated item matches original item.
if iszero(
and(
and(
// The offerer must match on both items.
eq(
mload(paramsPtr),
mload(
add(execution, Execution_offerer_offset)
)
),
// The conduit key must match on both items.
eq(
mload(
add(
paramsPtr,
OrderParameters_conduit_offset
)
),
mload(
add(
execution,
Execution_conduit_offset
)
)
)
),
// The itemType, token, and identifier must match.
eq(
dataHash,
keccak256(
offerItemPtr,
ReceivedItem_CommonParams_size
)
)
)
) {
// Throw if any of the requirements are not met.
throwInvalidFulfillmentComponentData()
}
}
// Write final amount to execution.
mstore(add(mload(execution), Common_amount_offset), amount)
// Determine if an error code is contained in the error buffer.
switch errorBuffer
case 1 {
// Store the MissingItemAmount error signature.
mstore(0, MissingItemAmount_error_signature)
// Return, supplying MissingItemAmount signature.
revert(0, MissingItemAmount_error_len)
}
case 2 {
// If the sum overflowed, panic.
throwOverflow()
}
}
}
/**
* @dev Internal pure function to aggregate a group of consideration items
* using supplied directives on which component items are candidates
* for aggregation, skipping items on orders that are not available.
*
* @param advancedOrders The orders to aggregate consideration
* items from.
* @param considerationComponents An array of FulfillmentComponent structs
* indicating the order index and item index
* of each candidate consideration item for
* aggregation.
* @param execution The execution to apply the aggregation to.
*/
function _aggregateValidFulfillmentConsiderationItems(
AdvancedOrder[] memory advancedOrders,
FulfillmentComponent[] memory considerationComponents,
Execution memory execution
) internal pure {
// Utilize assembly in order to efficiently aggregate the items.
assembly {
// Declare function for reverts on invalid fulfillment data.
function throwInvalidFulfillmentComponentData() {
// Store the InvalidFulfillmentComponentData error signature.
mstore(0, InvalidFulfillmentComponentData_error_signature)
// Return, supplying InvalidFulfillmentComponentData signature.
revert(0, InvalidFulfillmentComponentData_error_len)
}
// Declare function for reverts due to arithmetic overflows.
function throwOverflow() {
// Store the Panic error signature.
mstore(0, Panic_error_signature)
// Store the arithmetic (0x11) panic code as initial argument.
mstore(Panic_error_offset, Panic_arithmetic)
// Return, supplying Panic signature and arithmetic code.
revert(0, Panic_error_length)
}
// Get position in considerationComponents head.
let fulfillmentHeadPtr := add(considerationComponents, OneWord)
// Retrieve the order index using the fulfillment pointer.
let orderIndex := mload(mload(fulfillmentHeadPtr))
// Ensure that the order index is not out of range.
if iszero(lt(orderIndex, mload(advancedOrders))) {
throwInvalidFulfillmentComponentData()
}
// Read advancedOrders[orderIndex] pointer from its array head.
let orderPtr := mload(
// Calculate head position of advancedOrders[orderIndex].
add(add(advancedOrders, OneWord), mul(orderIndex, OneWord))
)
// Load consideration array pointer.
let considerationArrPtr := mload(
add(
// Read pointer to OrderParameters from the AdvancedOrder.
mload(orderPtr),
OrderParameters_consideration_head_offset
)
)
// Retrieve item index using an offset of the fulfillment pointer.
let itemIndex := mload(
add(mload(fulfillmentHeadPtr), Fulfillment_itemIndex_offset)
)
// Ensure that the order index is not out of range.
if iszero(lt(itemIndex, mload(considerationArrPtr))) {
throwInvalidFulfillmentComponentData()
}
// Retrieve consideration item pointer using the item index.
let considerationItemPtr := mload(
add(
// Get pointer to beginning of receivedItem.
add(considerationArrPtr, OneWord),
// Calculate offset to pointer for desired order.
mul(itemIndex, OneWord)
)
)
// Declare a variable for the final aggregated item amount.
let amount := 0
// Create variable to track errors encountered with amount.
let errorBuffer := 0
// Only add consideration amount to execution amount if numerator is
// greater than zero.
if mload(add(orderPtr, AdvancedOrder_numerator_offset)) {
// Retrieve amount pointer using consideration item pointer.
let amountPtr := add(considerationItemPtr, Common_amount_offset)
// Set the amount.
amount := mload(amountPtr)
// Set error bit if amount is zero.
errorBuffer := iszero(amount)
// Zero out amount on item to indicate it is credited.
mstore(amountPtr, 0)
}
// Retrieve ReceivedItem pointer from Execution.
let receivedItem := mload(execution)
// Set the item type on the received item.
mstore(receivedItem, mload(considerationItemPtr))
// Set the token on the received item.
mstore(
add(receivedItem, Common_token_offset),
mload(add(considerationItemPtr, Common_token_offset))
)
// Set the identifier on the received item.
mstore(
add(receivedItem, Common_identifier_offset),
mload(add(considerationItemPtr, Common_identifier_offset))
)
// Set the recipient on the received item.
mstore(
add(receivedItem, ReceivedItem_recipient_offset),
mload(
add(
considerationItemPtr,
ConsiderationItem_recipient_offset
)
)
)
// Calculate the hash of (itemType, token, identifier).
let dataHash := keccak256(
receivedItem,
ReceivedItem_CommonParams_size
)
// Get position one word past last element in head of array.
let endPtr := add(
considerationComponents,
mul(mload(considerationComponents), OneWord)
)
// Iterate over remaining offer components.
// prettier-ignore
for {} lt(fulfillmentHeadPtr, endPtr) {} {
// Increment position in considerationComponents head.
fulfillmentHeadPtr := add(fulfillmentHeadPtr, OneWord)
// Get the order index using the fulfillment pointer.
orderIndex := mload(mload(fulfillmentHeadPtr))
// Ensure the order index is in range.
if iszero(lt(orderIndex, mload(advancedOrders))) {
throwInvalidFulfillmentComponentData()
}
// Get pointer to AdvancedOrder element.
orderPtr := mload(
add(
add(advancedOrders, OneWord),
mul(orderIndex, OneWord)
)
)
// Only continue if numerator is not zero.
if iszero(
mload(add(orderPtr, AdvancedOrder_numerator_offset))
) {
continue
}
// Load consideration array pointer from OrderParameters.
considerationArrPtr := mload(
add(
// Get pointer to OrderParameters from AdvancedOrder.
mload(orderPtr),
OrderParameters_consideration_head_offset
)
)
// Get the item index using the fulfillment pointer.
itemIndex := mload(add(mload(fulfillmentHeadPtr), OneWord))
// Check if itemIndex is within the range of array.
if iszero(lt(itemIndex, mload(considerationArrPtr))) {
throwInvalidFulfillmentComponentData()
}
// Retrieve consideration item pointer using index.
considerationItemPtr := mload(
add(
// Get pointer to beginning of receivedItem.
add(considerationArrPtr, OneWord),
// Use offset to pointer for desired order.
mul(itemIndex, OneWord)
)
)
// Retrieve amount pointer using consideration item pointer.
let amountPtr := add(
considerationItemPtr,
Common_amount_offset
)
// Add offer amount to execution amount.
let newAmount := add(amount, mload(amountPtr))
// Update error buffer (1 = zero amount, 2 = overflow).
errorBuffer := or(
errorBuffer,
or(
shl(1, lt(newAmount, amount)),
iszero(mload(amountPtr))
)
)
// Update the amount to the new, summed amount.
amount := newAmount
// Zero out amount on original item to indicate it is credited.
mstore(amountPtr, 0)
// Ensure the indicated item matches original item.
if iszero(
and(
// Item recipients must match.
eq(
mload(
add(
considerationItemPtr,
ConsiderItem_recipient_offset
)
),
mload(
add(
receivedItem,
ReceivedItem_recipient_offset
)
)
),
// The itemType, token, identifier must match.
eq(
dataHash,
keccak256(
considerationItemPtr,
ReceivedItem_CommonParams_size
)
)
)
) {
// Throw if any of the requirements are not met.
throwInvalidFulfillmentComponentData()
}
}
// Write final amount to execution.
mstore(add(receivedItem, Common_amount_offset), amount)
// Determine if an error code is contained in the error buffer.
switch errorBuffer
case 1 {
// Store the MissingItemAmount error signature.
mstore(0, MissingItemAmount_error_signature)
// Return, supplying MissingItemAmount signature.
revert(0, MissingItemAmount_error_len)
}
case 2 {
// If the sum overflowed, panic.
throwOverflow()
}
}
}
}