Seaport - fulfillOrder

本文,主要描述 Seaport 中 fulfillOrder 的过程。前序准备工作可参见 Seaport - Order
在该过程中,可以看到,Seaport 中的交易过程更加灵活,ERC20,ERC721,ERC1155 之间,可以自由兑换。

1. Order 参数的基础验证

该过程主要涉及到订单时间的时效性、交易额度和交易方式的有效性等。

2. 关于 zone 的 EIP1271 验证

这里将涉及到 zone 的一个关键功能,提供 EIP1271 验证。即,用户可以借此对交易过程做更细粒度的控制,比如,指定交易人。

当处于严格模式下,zone 的校验包括:

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
// orderType > 1,则交易需要在严格模式下执行。
// 严格模式下,如果交易发起者不是 zone 或者 offerer,则需要在 zone 合约中完成基于 EIP 1271 的授权的校验
if (
uint256(orderType) > 1 &&
msg.sender != zone &&
msg.sender != offerer
) {
// If no extraData or criteria resolvers are supplied...
if (
advancedOrder.extraData.length == 0 &&
criteriaResolvers.length == 0
) {
// Perform minimal staticcall to the zone.
_callIsValidOrder(zone, orderHash, offerer, zoneHash);
} else {

// Otherwise, extra data or criteria resolvers were supplied; in
// that event, perform a more verbose staticcall to the zone.
//
bool success = _staticcall(
zone,
abi.encodeWithSelector(
ZoneInterface.isValidOrderIncludingExtraData.selector,
orderHash,
msg.sender,
advancedOrder,
priorOrderHashes,
criteriaResolvers
)
);

// Ensure call was successful and returned correct magic value.
_assertIsValidOrderStaticcallSuccess(success, orderHash);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function _callIsValidOrder(
address zone,
bytes32 orderHash,
address offerer,
bytes32 zoneHash
) internal view {
// Perform minimal staticcall to the zone.
bool success = _staticcall(
zone,
abi.encodeWithSelector(
ZoneInterface.isValidOrder.selector,
orderHash,
msg.sender,
offerer,
zoneHash
)
);

// Ensure call was successful and returned the correct magic value.
_assertIsValidOrderStaticcallSuccess(success, orderHash);
}

3. 验证 Order 状态和签名

验证 Order Status 和 Order signature. 注意,这里,Order 的签名,必须是由 offerer 完成的。

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
function _verifySignature(
address offerer,
bytes32 orderHash,
bytes memory signature
) internal view {
// Skip signature verification if the offerer is the caller.
if (offerer == msg.sender) {
return;
}

// Derive EIP-712 digest using the domain separator and the order hash.
bytes32 digest = _deriveEIP712Digest(_domainSeparator(), orderHash);

// Ensure that the signature for the digest is valid for the offerer.
_assertValidSignature(offerer, digest, signature);
}

function _deriveEIP712Digest(bytes32 domainSeparator, bytes32 orderHash)
internal
pure
returns (bytes32 value)
{
// Leverage scratch space to perform an efficient hash.
assembly {
// Place the EIP-712 prefix at the start of scratch space.
mstore(0, EIP_712_PREFIX)

// Place the domain separator in the next region of scratch space.
mstore(EIP712_DomainSeparator_offset, domainSeparator)

// Place the order hash in scratch space, spilling into the first
// two bytes of the free memory pointer — this should never be set
// as memory cannot be expanded to that size, and will be zeroed out
// after the hash is performed.
mstore(EIP712_OrderHash_offset, orderHash)

// Hash the relevant region (65 bytes).
value := keccak256(0, EIP712_DigestPayload_size)

// Clear out the dirtied bits in the memory pointer.
mstore(EIP712_OrderHash_offset, 0)
}
}

Order Hash 的计算 可以参见上一篇的 Order 介绍。

4. 条件解析

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
// CriteriaResolution.sol
function _applyCriteriaResolvers(
AdvancedOrder[] memory advancedOrders,
CriteriaResolver[] memory criteriaResolvers
) internal pure {
// Skip overflow checks as all for loops are indexed starting at zero.
unchecked {
// Retrieve length of criteria resolvers array and place on stack.
uint256 totalCriteriaResolvers = criteriaResolvers.length;

// Retrieve length of orders array and place on stack.
uint256 totalAdvancedOrders = advancedOrders.length;

// Iterate over each criteria resolver.
// 遍历 resolvers
for (uint256 i = 0; i < totalCriteriaResolvers; ++i) {
// 从 resolvers 中取出 resolver
CriteriaResolver memory criteriaResolver = (
criteriaResolvers[i]
);

// 找到 resolver 所要处理的 order 所对应的 index
uint256 orderIndex = criteriaResolver.orderIndex;
// 最基本的,需要确保 resolver 所对应的 order 应在 orders 中
if (orderIndex >= totalAdvancedOrders) {
revert OrderCriteriaResolverOutOfRange();
}

// Skip criteria resolution for order if not fulfilled.
// 如果其所对应的分子为 0,则不需要再做判断。
// 所以,分子和 criteria resolver 之间存在什么关系呢?? maybe 答案不在这里...
if (advancedOrders[orderIndex].numerator == 0) {
continue;
}

// Retrieve the parameters for the order.
OrderParameters memory orderParameters = (
advancedOrders[orderIndex].parameters
);

// 具体是对应这 offer 中的那个 item 的索引 index
uint256 componentIndex = criteriaResolver.index;
// Declare values for item's type and criteria.
ItemType itemType;
uint256 identifierOrCriteria;

// 所对应的 orderItem 来自于 offer
if (criteriaResolver.side == Side.OFFER) {
// 取出 order 中的 offer
OfferItem[] memory offer = orderParameters.offer;
// 至少应确保 resolver 所对应的 offer item index 应在 offer 中
if (componentIndex >= offer.length) {
revert OfferCriteriaResolverOutOfRange();
}

// 检索到 resolver 最终所对应的 offerItem
OfferItem memory offerItem = offer[componentIndex];

// 读取 offerItem 的类型
itemType = offerItem.itemType;
identifierOrCriteria = offerItem.identifierOrCriteria;

ItemType newItemType;
assembly {
// 验证之后,将去除 criteria 化: 4 -> 2; 5 -> 3
newItemType := sub(3, eq(itemType, 4))
}
offerItem.itemType = newItemType;
// Optimistically update identifier w/ supplied identifier.
offerItem.identifierOrCriteria = criteriaResolver
.identifier;
} else {
// Otherwise, the resolver refers to a consideration item.
ConsiderationItem[] memory consideration = (
orderParameters.consideration
);

// Ensure that the component index is in range.
if (componentIndex >= consideration.length) {
revert ConsiderationCriteriaResolverOutOfRange();
}

// Retrieve relevant item using order and component index.
ConsiderationItem memory considerationItem = (
consideration[componentIndex]
);

// Read item type and criteria from memory & place on stack.
itemType = considerationItem.itemType;
identifierOrCriteria = (
considerationItem.identifierOrCriteria
);

// Optimistically update item type to remove criteria usage.
// Use assembly to operate on ItemType enum as a number.
ItemType newItemType;
assembly {
// Item type 4 becomes 2 and item type 5 becomes 3.
newItemType := sub(3, eq(itemType, 4))
}
considerationItem.itemType = newItemType;

// Optimistically update identifier w/ supplied identifier.
considerationItem.identifierOrCriteria = (
criteriaResolver.identifier
);
}

// Ensure the specified item type indicates criteria usage.
if (!_isItemWithCriteria(itemType)) {
revert CriteriaNotEnabledForItem();
}

// 为 0,则代表是 erc20 或者 ether 类型的 offerItem
if (identifierOrCriteria != uint256(0)) {
// 基于 merkle tree 的数据验证
_verifyProof(
// 叶子
criteriaResolver.identifier,
// 根 (root)
identifierOrCriteria,
// 证据 (proof)
criteriaResolver.criteriaProof
);
}
}

// 遍历一遍 orders,所有 order 这时都不再是 criteria 的
for (uint256 i = 0; i < totalAdvancedOrders; ++i) {
// Retrieve the advanced order.
AdvancedOrder memory advancedOrder = advancedOrders[i];

// Skip criteria resolution for order if not fulfilled.
if (advancedOrder.numerator == 0) {
continue;
}

// Retrieve the parameters for the order.
OrderParameters memory orderParameters = (
advancedOrder.parameters
);

// Read consideration length from memory and place on stack.
uint256 totalItems = orderParameters.consideration.length;

// Iterate over each consideration item on the order.
for (uint256 j = 0; j < totalItems; ++j) {
// Ensure item type no longer indicates criteria usage.
if (
_isItemWithCriteria(
orderParameters.consideration[j].itemType
)
) {
revert UnresolvedConsiderationCriteria();
}
}

// Read offer length from memory and place on stack.
totalItems = orderParameters.offer.length;

// Iterate over each offer item on the order.
for (uint256 j = 0; j < totalItems; ++j) {
// Ensure item type no longer indicates criteria usage.
if (
_isItemWithCriteria(orderParameters.offer[j].itemType)
) {
revert UnresolvedOfferCriteria();
}
}
}
}
}

CriteriaResolution 中 Merkle 验证过程

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
function _verifyProof(
uint256 leaf,
uint256 root,
bytes32[] memory proof
) internal pure {
bool isValid;
assembly {
// 将 leaf 存储到 0 的位置
mstore(0, leaf)
// 计算 leaf 的 hash
let computedHash := keccak256(0, OneWord)
// proof 数组中的第一个元素
let data := add(proof, OneWord)
// 迭代 proof 中的数据
for {
let end := add(data, shl(5, mload(proof)))
} lt(data, end) {
// Increment by one word at a time.
data := add(data, OneWord)
} {
// 加载 proof 中的元素
let loadedData := mload(data)
// shl(x, y) 将 y 逻辑左移 x 位
// gt(x, y) 如果 x > y 为 1,否则为 0
// 所以,这里实际上是确定 com
let scratch := shl(5, gt(computedHash, loadedData))
// 将 computedHash 和 loadedHash 从小到大排排坐
mstore(scratch, computedHash)
// xor(x, y) x 和 y 的按位异或
mstore(xor(scratch, OneWord), loadedData)
// 计算排好序的 computedHash 和 loadedData 的 Hash,作为新的 computedHash
computedHash := keccak256(0, TwoWords)
}
// 最后,验证合法性。计算出来的 computedHash 是否等于 root
isValid := eq(computedHash, root)
}
// Revert if computed hash does not equal supplied root.
if (!isValid) {
revert InvalidProof();
}
}

5. item 转移

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
// https://github.com/ProjectOpenSea/seaport/blob/171f2cd7faf13b2bf0455851499f1981274977f7/contracts/lib/Executor.sol#L49
function _transfer(
ReceivedItem memory item,
address from,
bytes32 conduitKey,
bytes memory accumulator
) internal {
// If the item type indicates Ether or a native token...
if (item.itemType == ItemType.NATIVE) {
// Ensure neither the token nor the identifier parameters are set.
if ((uint160(item.token) | item.identifier) != 0) {
revert UnusedItemParameters();
}

// transfer the native tokens to the recipient.
_transferEth(item.recipient, item.amount);
} else if (item.itemType == ItemType.ERC20) {
// Ensure that no identifier is supplied.
if (item.identifier != 0) {
revert UnusedItemParameters();
}

// Transfer ERC20 tokens from the source to the recipient.
_transferERC20(
item.token,
from,
item.recipient,
item.amount,
conduitKey,
accumulator
);
} else if (item.itemType == ItemType.ERC721) {
// Transfer ERC721 token from the source to the recipient.
_transferERC721(
item.token,
from,
item.recipient,
item.identifier,
item.amount,
conduitKey,
accumulator
);
} else {
// Transfer ERC1155 token from the source to the recipient.
_transferERC1155(
item.token,
from,
item.recipient,
item.identifier,
item.amount,
conduitKey,
accumulator
);
}
}

需要注意的是,在这里 我们可以看到,作为 offerer,在执行 fulfill 时,是不允许提供 Native Coin 的。

© 2024 YueGS