Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move offer/consideration item checks for contract orders into decoder function #24

Merged
merged 1 commit into from
Feb 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading