Skip to content

Commit

Permalink
Move offer/consideration item checks for contract orders into decoder…
Browse files Browse the repository at this point in the history
… logic to reduce total loop iterations
  • Loading branch information
d1ll0n committed Feb 16, 2024
1 parent 30e4229 commit f93d2da
Show file tree
Hide file tree
Showing 2 changed files with 216 additions and 178 deletions.
227 changes: 209 additions & 18 deletions src/core/lib/ConsiderationDecoder.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import {
AdvancedOrderPlusOrderParameters_head_size,
Common_amount_offset,
Common_endAmount_offset,
Common_identifier_offset,
Common_token_offset,
ConsiderationItem_recipient_offset,
ConsiderationItem_size_with_length,
ConsiderationItem_size,
Expand Down Expand Up @@ -866,7 +868,7 @@ contract ConsiderationDecoder {
* @return offer The decoded offer array.
* @return consideration The decoded consideration array.
*/
function _decodeGenerateOrderReturndata()
function _decodeGenerateOrderReturndata(MemoryPointer originalOffer, MemoryPointer originalConsideration)
internal
pure
returns (
Expand Down Expand Up @@ -943,23 +945,34 @@ contract ConsiderationDecoder {
}

if iszero(invalidEncoding) {
offer :=
let invalidSpentItems, invalidReceivedItems
offer, invalidSpentItems :=
copySpentItemsAsOfferItems(
add(offsetOffer, OneWord), offerLength
originalOffer,
add(offsetOffer, OneWord),
offerLength
)

consideration :=
consideration, invalidReceivedItems :=
copyReceivedItemsAsConsiderationItems(
add(offsetConsideration, OneWord), considerationLength
originalConsideration,
add(offsetConsideration, OneWord),
considerationLength
)
invalidEncoding := or(invalidSpentItems, invalidReceivedItems)
}

function copySpentItemsAsOfferItems(rdPtrHead, length) -> mPtrLength
function copySpentItemsAsOfferItems(
mPtrLengthOriginal, rdPtrHeadSpentItems, length
) -> mPtrLength, invalidSpentItems
{
// Retrieve the current free memory pointer.
mPtrLength := mload(FreeMemoryPointerSlot)

// Allocate memory for the array.
// Cache the original offer array length
let originalOfferLength := mload(mPtrLengthOriginal)

// Allocate memory for the new array.
mstore(
FreeMemoryPointerSlot,
add(
Expand All @@ -975,14 +988,58 @@ contract ConsiderationDecoder {
let headOffsetFromLength := OneWord
let headSizeWithLength := shl(OneWordShift, add(1, length))
let mPtrTailNext := add(mPtrLength, headSizeWithLength)
let mPtrTailOriginalNext := add(
mPtrLengthOriginal,
shl(OneWordShift, add(1, originalOfferLength))
)

let headSizeToCompareWithLength := shl(OneWordShift, add(1, min(length, originalOfferLength)))

// Iterate over each new element with a corresponding original item
// For each original offer item, check that:
// - There is a corresponding new spent item.
// - The original and new items match with compareItems.
// - The new offer's amount is greater than or equal to the original amount.
invalidSpentItems := gt(originalOfferLength, length)
for { } lt(headOffsetFromLength, headSizeToCompareWithLength) { } {
// Write the memory pointer to the accompanying head offset.
mstore(add(mPtrLength, headOffsetFromLength), mPtrTailNext)

// Copy itemType, token, identifier and amount.
returndatacopy(mPtrTailNext, rdPtrHeadSpentItems, SpentItem_size)

// Iterate over each element.
let newAmount := mload(add(mPtrTailNext, Common_amount_offset))

// Copy amount to endAmount.
mstore(
add(mPtrTailNext, Common_endAmount_offset),
newAmount
)

let originalAmount := mload(add(mPtrTailOriginalNext, Common_amount_offset))
invalidSpentItems := or(
invalidSpentItems,
or(
compareItems(mPtrTailOriginalNext, mPtrTailNext),
gt(originalAmount, newAmount)
)
)

// Update read pointer, next tail pointer for new and
// original, and head offset.
rdPtrHeadSpentItems := add(rdPtrHeadSpentItems, SpentItem_size)
mPtrTailNext := add(mPtrTailNext, OfferItem_size)
mPtrTailOriginalNext := add(mPtrTailOriginalNext, OfferItem_size)
headOffsetFromLength := add(headOffsetFromLength, OneWord)
}

// Iterate over each element without a corresponding original item
for { } lt(headOffsetFromLength, headSizeWithLength) { } {
// Write the memory pointer to the accompanying head offset.
mstore(add(mPtrLength, headOffsetFromLength), mPtrTailNext)

// Copy itemType, token, identifier and amount.
returndatacopy(mPtrTailNext, rdPtrHead, SpentItem_size)
returndatacopy(mPtrTailNext, rdPtrHeadSpentItems, SpentItem_size)

// Copy amount to endAmount.
mstore(
Expand All @@ -991,17 +1048,20 @@ contract ConsiderationDecoder {
)

// Update read pointer, next tail pointer, and head offset.
rdPtrHead := add(rdPtrHead, SpentItem_size)
rdPtrHeadSpentItems := add(rdPtrHeadSpentItems, SpentItem_size)
mPtrTailNext := add(mPtrTailNext, OfferItem_size)
headOffsetFromLength := add(headOffsetFromLength, OneWord)
}
}

function copyReceivedItemsAsConsiderationItems(rdPtrHead, length) ->
mPtrLength
function copyReceivedItemsAsConsiderationItems(
mPtrLengthOriginal, rdPtrHeadReceivedItems, length
) -> mPtrLength, invalidReceivedItems
{
// Retrieve the current free memory pointer.
mPtrLength := mload(FreeMemoryPointerSlot)
// Cache the original consideration array length
let originalConsiderationLength := mload(mPtrLengthOriginal)

// Allocate memory for the array.
mstore(
Expand All @@ -1022,32 +1082,163 @@ contract ConsiderationDecoder {
let headOffsetFromLength := OneWord
let headSizeWithLength := shl(OneWordShift, add(1, length))
let mPtrTailNext := add(mPtrLength, headSizeWithLength)
let mPtrTailOriginalNext := add(
mPtrLengthOriginal,
shl(OneWordShift, add(1, originalConsiderationLength))
)

let headSizeToCompareWithLength := shl(
OneWordShift,
add(1, min(length, originalConsiderationLength))
)

// Iterate over each new element with a corresponding original item.
// For each new received item, check that:
// - There is a corresponding original consideration item.
// - The items match with compareItems.
// - The new amount is less than or equal to the original amount.
// - The items have the same recipient if the original's was not null.
invalidReceivedItems := gt(length, originalConsiderationLength)
for { } lt(headOffsetFromLength, headSizeToCompareWithLength) { } {
// Write the memory pointer to the accompanying head offset.
mstore(add(mPtrLength, headOffsetFromLength), mPtrTailNext)

// Copy itemType, token, identifier, amount and recipient.
returndatacopy(
mPtrTailNext,
rdPtrHeadReceivedItems,
ReceivedItem_size
)

// Iterate over each element.
// Copy amount to consideration item's recipient offset.
returndatacopy(
add(mPtrTailNext, ConsiderationItem_recipient_offset),
add(rdPtrHeadReceivedItems, Common_amount_offset),
OneWord
)

let newAmount := mload(add(mPtrTailNext, Common_amount_offset))
let originalAmount := mload(add(mPtrTailOriginalNext, Common_amount_offset))

// Compare items' item type, token, and identifier, ensure they have the same
// recipient and that the new amount is less than or equal to the original amount.
invalidReceivedItems := or(
invalidReceivedItems,
or(
compareItems(mPtrTailOriginalNext, mPtrTailNext),
or(
gt(newAmount, originalAmount),
checkRecipients(
mload(add(mPtrTailOriginalNext, ReceivedItem_recipient_offset)),
mload(add(mPtrTailNext, ReceivedItem_recipient_offset))
)
)
)
)

// Update read pointer, next tail pointer, and head offset.
rdPtrHeadReceivedItems := add(rdPtrHeadReceivedItems, ReceivedItem_size)
mPtrTailNext := add(mPtrTailNext, ConsiderationItem_size)
mPtrTailOriginalNext := add(mPtrTailOriginalNext, ConsiderationItem_size)
headOffsetFromLength := add(headOffsetFromLength, OneWord)
}

// Iterate over each new element without a corresponding original item
for { } lt(headOffsetFromLength, headSizeWithLength) { } {
// Write the memory pointer to the accompanying head offset.
mstore(add(mPtrLength, headOffsetFromLength), mPtrTailNext)

// Copy itemType, token, identifier, amount and recipient.
returndatacopy(
mPtrTailNext,
rdPtrHead,
rdPtrHeadReceivedItems,
ReceivedItem_size
)

// Copy amount to consideration item's recipient offset.
returndatacopy(
add(mPtrTailNext, ConsiderationItem_recipient_offset),
add(rdPtrHead, Common_amount_offset),
add(rdPtrHeadReceivedItems, Common_amount_offset),
OneWord
)

// Update read pointer, next tail pointer, and head offset.
rdPtrHead := add(rdPtrHead, ReceivedItem_size)
rdPtrHeadReceivedItems := add(rdPtrHeadReceivedItems, ReceivedItem_size)
mPtrTailNext := add(mPtrTailNext, ConsiderationItem_size)
headOffsetFromLength := add(headOffsetFromLength, OneWord)
}
}

/**
* @dev Yul function to check the compatibility of two offer or
* consideration items for contract orders. Note that the itemType
* and identifier are reset in cases where criteria = 0 (collection-
* wide offers), which means that a contract offerer has full latitude
* to choose any identifier it wants mid-flight, in contrast to the
* normal behavior, where the fulfiller can pick which identifier to
* receive by providing a CriteriaResolver.
*
* @param originalItem The original offer or consideration item.
* @param newItem The new offer or consideration item.
*
* @return isInvalid Error buffer indicating if items are incompatible.
*/
function compareItems(originalItem, newItem) -> isInvalid {
let itemType := mload(originalItem)
let identifier := mload(add(originalItem, Common_identifier_offset))

// Set returned identifier for criteria-based items w/ criteria = 0
if and(gt(itemType, 3), iszero(identifier)) {
// replace item type
itemType := sub(3, eq(itemType, 4))
identifier := mload(add(newItem, Common_identifier_offset))
}

isInvalid :=
iszero(
and(
// originalItem.token == newItem.token &&
// originalItem.itemType == newItem.itemType
and(
eq(
mload(add(originalItem, Common_token_offset)),
mload(add(newItem, Common_token_offset))
),
eq(itemType, mload(newItem))
),
// originalItem.identifier == newItem.identifier
eq(
identifier,
mload(add(newItem, Common_identifier_offset))
)

)
)
}

/**
* @dev Internal pure function to check the compatibility of two recipients
* on consideration items for contract orders. This check is skipped if
* no recipient is originally supplied.
*
* @param originalRecipient The original consideration item recipient.
* @param newRecipient The new consideration item recipient.
*
* @return isInvalid Error buffer indicating if recipients are incompatible.
*/
function checkRecipients(originalRecipient, newRecipient) -> isInvalid {
isInvalid :=
iszero(
or(
iszero(originalRecipient),
eq(newRecipient, originalRecipient)
)
)
}

function min(a, b) -> c {
c := add(b, mul(lt(a, b), sub(a, b)))
}
}
}

Expand All @@ -1062,15 +1253,15 @@ contract ConsiderationDecoder {
* error buffer, offer array, and consideration array.
*/
function _convertGetGeneratedOrderResult(
function()
function(MemoryPointer, MemoryPointer)
internal
pure
returns (uint256, MemoryPointer, MemoryPointer) inFn
)
internal
pure
returns (
function() internal pure returns (uint256, OfferItem[] memory, ConsiderationItem[] memory)
function(OfferItem[] memory, ConsiderationItem[] memory) internal pure returns (uint256, OfferItem[] memory, ConsiderationItem[] memory)
outFn
)
{
Expand Down
Loading

0 comments on commit f93d2da

Please sign in to comment.